I was looking into logging actions for example when you create a new user it sends it to the logger etc.. so every action is logged. I can see how the logger.info sends information into the development.log file.
I was wondering how I Would set-up a different file e.g. users.log and then when I log a line or variable, it saves inside that log file instead of the development.log?
Ruby has a Logger class in its standard lib: http://ruby-doc.org/stdlib-2.1.0/libdoc/logger/rdoc/Logger.html
You would instantiate that and pass it the file path of your new log file, like this:
user_log = File.open('logs/users.log', File::WRONLY | File::APPEND)
You can place that in a controller method that your controllers can use. The first string argument is the path to the log file, and the following are opening the file for writing and appending only (so that each log line is added to the log rather than overwriting it each time).
You can customize the format of each log line by setting a formatter:
user_log.formatter = proc { |severity, datetime, progname, msg|
"#{severity}, #{datetime}, #{progname}, #{msg.dump}"
}
You can specify the file path used in the config file, which can vary according to the environment, as so:
config.paths.log = "/some/path/#{Rails.env}.log"
If you want to create different log files for each model, you can simply create a logger object when needed, as explained in this answer.
However if you just want to somehow mark different logs according to where they were generated, it may be easier to use tagged logging:
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff"
logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
Related
I have this code in one of my tests:
it 'returns ids when successful' do
allow_any_instance_of(Importer).to receive(:import).and_return('12589', '12590', '12591', '12592', '12593', '12594')
expect(#dispatcher.run).to eq(['12589', '12590', '12591', '12592', '12593', '12594'])
end
The test fails because it only return the first value:
expected: ["12589", "12590", "12591", "12592", "12593", "12594"]
got: ["12589", "12589", "12589", "12589", "12589", "12589"]
I just saw that #and_return's capability of returning multiple values only works when used with #allow.
What can I do for #allow_any_instance_of to get this behaviour?
EDIT:
The class I am testing is called Dispatcher. It takes an xml file, and splits it into parts concerning exactly one object. Each of those splitted parts is taken by the Importer which returns exactly one ID. The Dispatcher then creates an Array from those Ids. So, no, I am not expecting an array to be returned by the Importer.
The class I am testing, Dispatcher, calls Importer for every file it finds in an input directory.
Here's what should work (intercept importer creation)
class Dispatcher
def run
files.each do |file|
create_importer(file).import
end
end
def create_importer(file)
::Importer.new(file)
end
end
# spec
let(:fake_importer) { ::Importer.new }
before do
allow(#dispatcher).to receive(:create_importer).and_return(fake_importer)
allow(fake_importer).to receive(:import).and_return(your, multiple, values, here)
end
In our Grails web applications, we'd like to use external configuration files so that we can change the configuration without releasing a new version. We'd also like these files to be outside of the application directory so that they stay unchanged during continuous integration.
The last thing we need to do is to make sure the external configuration files exist. If they don't, then we'd like to create them, fill them with predefined content (production environment defaults) and then use them as if they existed before. This allows any administrator to change settings of the application without detailed knowledge of the options actually available.
For this purpose, there's a couple of files within web-app/WEB-INF/conf ready to be copied to the external configuration location upon the first run of the application.
So far so good. But we need to do this before the application is initialized so that production-related modifications to data sources definitions are taken into account.
I can do the copy-and-load operation inside the Config.groovy file, but I don't know the absolute location of the WEB-INF/conf directory at the moment.
How can I get the location during this early phase of initialization? Is there any other solution to the problem?
There is a best practice for this.
In general, never write to the folder where the application is deployed. You have no control over it. The next rollout will remove everything you wrote there.
Instead, leverage the builtin configuration capabilities the real pro's use (Spring and/or JPA).
JNDI is the norm for looking up resources like databases, files and URL's.
Operations will have to configure JNDI, but they appreciate the attention.
They also need an initial set of configuration files, and be prepared to make changes at times as required by the development team.
As always, all configuration files should be in your source code repo.
I finally managed to solve this myself by using the Java's ability to locate resources placed on the classpath.
I took the .groovy files later to be copied outside, placed them into the grails-app/conf directory (which is on the classpath) and appended a suffix to their name so that they wouldn't get compiled upon packaging the application. So now I have *Config.groovy files containing configuration defaults (for all environments) and *Config.groovy.production files containing defaults for production environment (overriding the precompiled defaults).
Now - Config.groovy starts like this:
grails.config.defaults.locations = [ EmailConfig, AccessConfig, LogConfig, SecurityConfig ]
environments {
production {
grails.config.locations = ConfigUtils.getExternalConfigFiles(
'.production',
"${userHome}${File.separator}.config${File.separator}${appName}",
'AccessConfig.groovy',
'Config.groovy',
'DataSource.groovy',
'EmailConfig.groovy',
'LogConfig.groovy',
'SecurityConfig.groovy'
)
}
}
Then the ConfigUtils class:
public class ConfigUtils {
// Log4j may not be initialized yet
private static final Logger LOG = Logger.getGlobal()
public static def getExternalConfigFiles(final String defaultSuffix, final String externalConfigFilesLocation, final String... externalConfigFiles) {
final def externalConfigFilesDir = new File(externalConfigFilesLocation)
LOG.info "Loading configuration from ${externalConfigFilesDir}"
if (!externalConfigFilesDir.exists()) {
LOG.warning "${externalConfigFilesDir} not found. Creating..."
try {
externalConfigFilesDir.mkdirs()
} catch (e) {
LOG.severe "Failed to create external configuration storage. Default configuration will be used."
e.printStackTrace()
return []
}
}
final def cl = ConfigUtils.class.getClassLoader()
def result = []
externalConfigFiles.each {
final def file = new File(externalConfigFilesDir, it)
if (file.exists()) {
result << file.toURI().toURL()
return
}
final def error = false
final def defaultFileURL = cl.getResource(it + defaultSuffix)
final def defaultFile
if (defaultFileURL) {
defaultFile = new File(defaultFileURL.toURI())
error = !defaultFile.exists();
} else {
error = true
}
if (error) {
LOG.severe "Neither of ${file} or ${defaultFile} exists. Skipping..."
return
}
LOG.warning "${file} does not exist. Copying ${defaultFile} -> ${file}..."
try {
FileUtils.copyFile(defaultFile, file)
} catch (e) {
LOG.severe "Couldn't copy ${defaultFile} -> ${file}. Skipping..."
e.printStackTrace()
return
}
result << file.toURI().toURL()
}
return result
}
}
For example, it generated a log(my_work.log) content as:
I, [2015-05-14T00:00:00.000000 #5590] INFO -- : Work started.
I want to test if my_work.log has content Work started., how to do?
I don't want to match all line include datetime, because that contains #5590, I can't stub it.
You can pass in an instance of StringIO when initializing Logger to capture the output and then match on the expected content:
require 'logger'
describe "log" do
let(:log) { StringIO.new }
let(:logger) { Logger.new(log) }
let(:expected) { "Work started" }
it "should add the expected content to the log" do
logger.info(expected)
log.rewind
expect(log.read).to match(/.+#{expected}$/)
end
end
Rails.logger uses some methods to log things, for example:
debug
info
fatal
warn
So in your case you use info, to log something, instead loking for a match, you can detect if method info was called:
it 'logs exception' do
# some code
expect(Rails.logger).to receive(:info)
# execute the line that logs something
end
Even you can add parameters to receive method with reserved word with:
expect(Rails.logger).to receive(:info).with('Work started.')
This cause you need to specify something
Check rspec and rails logger
Also check this stackoverflow post
With RSpec's output matcher (introduced in 3.0) you can do the following:
expect { my_method }.to output("my message").to_stdout
expect { my_method }.to output("my error").to_stderr
In case of libraries such as Logger or Logging you may have to use output.to_<stdout/stderr>_from_any_process.
It's simple, clean and will test whether your messages actually reach the output.
I'm running a test with Grails 2.3.8 and an external configuration, yet the value doesn't seem to be coming through. I've tried this in older versions and am not seeing the same error. Did something change or am I missing something that I fat fingered? I am using an absolute path and the file definitely exists because Grails sees the key.
Config.groovy
reliable.string = "This string is working"
grails.config.locations = ["file:/home/howes/Project/project/test-config.groovy"]
/home/howes/Project/project/test-config.groovy
test.externalstring = "this is a test"
To test it I made a controller and just called grailsApplication to pull out the values. The first time I load the page I get a blank map, and when I refresh I see the key but no value. To make sure I am pulling everything correctly I am outputting the test-config.groovy item and one from the basic Config.groovy.
TestContoller.groovy
class TestController {
def grailsApplication
def index() {
println grailsApplication.config.test
println grailsApplication.config.reliable
return [ext:grailsApplication.config.test.externalstring, ext2:grailsApplication.config.reliable.string]
}
}
Console Output (First Load)
[:]
[string:This string is working]
Console Output (Page Refresh)
[externalstring:[:]]
[string:This string is working]
What am I missing here?
I have a configuration parameter in my BuildConfig.groovy that requires I put a lot of directories in for each plugin I have. I like to create a simple method that will generate all of those directories for me. I know groovy config files are groovy code, but I can't seem to find any information related to the preferred method for creating helper methods. (for example mavenRepo, grailsPlugins(), grailsHome(), etc). If I want to create my own helper method where would I put it so I can call it like so:
someProperty = myHelperMethod()
A bit of an update. I wrote this method directly in my BuildConfig.groovy file and it works! But I'd like to move it out and organize it better. How do I write such methods and expose those to config files like BuildConfig.groovy?
def pluginDirs() {
List dirs = []
String trash = "[:]/"
grails.plugin.location.each() { name, plugin ->
if( plugin.startsWith(trash) ) {
plugin = plugin.substring(trash.length())
}
dirs << "${plugin}/src/groovy" <<
"${plugin}/grails-app/controllers" <<
"${plugin}/grails-app/domain" <<
"${plugin}/grails-app/services" <<
"${plugin}/grails-app/taglib" <<
"${plugin}/grails-app/utils"
}
return dirs
}
Extra-credit: Some of these plugins use ${basedir} to define their path, but when I run one of my scripts ${basedir} isn't defined to it puts [:] trash in my URLs. That's why that ugly code is in there.