Compare commits
9
Commits
82e671ba58
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69e7e8d9fa | ||
|
|
b4f00010c2 | ||
|
|
7b0d516408 | ||
|
|
484a1113e2 | ||
|
|
daa06a5acf | ||
|
|
ca7d455cde | ||
|
|
f6acc9dcf7 | ||
|
|
e4212b265e | ||
|
|
26e679a5d5 |
+65
-100
@@ -1,120 +1,85 @@
|
||||
require "time"
|
||||
require "colorize"
|
||||
require "log"
|
||||
|
||||
module Logger
|
||||
|
||||
annotation LoggerWatcher
|
||||
# This allows the user to mark an instance variable to be kept as context when
|
||||
# any ::Logger generated macro is used within and outside of class/instance methods
|
||||
annotation LogContext
|
||||
end
|
||||
|
||||
struct LOG_LEVEL
|
||||
getter value : Int32
|
||||
getter color : Symbol
|
||||
delegate in?, to: @value
|
||||
# Tells the ::Logger macros that if any log macros get run in a annotated method
|
||||
# that it will keep track of the arguments of the method as it runs. Alongside
|
||||
# providing the user the ability to exclude arguments (like when passing a
|
||||
# plain text password into a method)
|
||||
#
|
||||
# For this you will need to make sure to use the exclude: key with an array of
|
||||
# method names (strings %w[ abc ] is easiest)
|
||||
annotation LogMethod
|
||||
end
|
||||
|
||||
def initialize(@value : Int32, @color : Symbol)
|
||||
end
|
||||
|
||||
def to_s
|
||||
LOG_LEVELS.keys[@value].to_s.downcase
|
||||
end
|
||||
|
||||
{% for op, i in %w[ < > == <= >= ] %}
|
||||
def {{op.id}} (other : LOG_LEVEL)
|
||||
@value {{op.id}} other.value
|
||||
end
|
||||
# Macro for doing the actual logging based on how the user has
|
||||
# the Logger annotations
|
||||
macro log_with_context(level, message)
|
||||
{% if @def && @def.annotation(LogMethod) %}
|
||||
{% anno = @def.annotation(LogMethod) %}
|
||||
{% exclude = (anno[:exclude] || [] of MacroId).map(&.id) %}
|
||||
{% args = @def.args.reject{|a| exclude.includes?(a.name)} %}
|
||||
{% unless args.empty? %}
|
||||
Log.with_context( {% for arg in args %} {{arg.name}}: {{arg.name}}, {% end %} ) do
|
||||
Log.{{level}} { generate_log_line {{message}} }
|
||||
end
|
||||
{% else %}
|
||||
Log.{{level}} { generate_log_line {{message}} }
|
||||
{% end %}
|
||||
{% elsif @type && @def %}
|
||||
{% if @type.class? && @type.methods.includes?(@def) %}
|
||||
{% if vars = @type.instance_vars.select(&.annotation(LogContext)) %}
|
||||
Log.with_context( {% for var in vars %} {{var.name}}: @{{var.name}}, {% end %} ) do
|
||||
Log.{{level}} { generate_log_line {{message}} }
|
||||
end
|
||||
{% else %}
|
||||
Log.{{level}} { generate_log_line {{message}} }
|
||||
{% end %}
|
||||
{% elsif @type.module? %}
|
||||
Log.{{level}} { generate_log_line {{message}} }
|
||||
{% end %}
|
||||
{% else %}
|
||||
Log.{{level}} { generate_log_line {{message}} }
|
||||
{% end %}
|
||||
end
|
||||
|
||||
LOG_LEVELS = {
|
||||
"DEBUG" => LOG_LEVEL.new(value: 0, color: :green),
|
||||
"INFO" => LOG_LEVEL.new(value: 1, color: :blue),
|
||||
"WARN" => LOG_LEVEL.new(value: 2, color: :yellow),
|
||||
"ERROR" => LOG_LEVEL.new(value: 3, color: :red),
|
||||
"FATAL" => LOG_LEVEL.new(value: 4, color: :red)
|
||||
}
|
||||
|
||||
@@min_level : LOG_LEVEL = LOG_LEVELS["INFO"]
|
||||
@@max_level : LOG_LEVEL = LOG_LEVELS["FATAL"]
|
||||
@@log_file : String = ""
|
||||
@@debug : Bool = false
|
||||
|
||||
LOG_MUTEX = ::Mutex.new
|
||||
|
||||
macro generate_log_line
|
||||
{% verbatim do %}
|
||||
{% if @type && @def %}
|
||||
"{{(@type.module? ? "Module" : "Class").id}}({{@type.name.id}})" + " : " + "Method({{@def.name}})" + " : "
|
||||
{% elsif @type %}
|
||||
"{{(@type.module? ? "Module" : "Class").id}}({{@type.name.id}})" + " : "
|
||||
{% elsif @def %}
|
||||
"Method({{@def.name}})" + " : "
|
||||
# Generates the log-line (everything that isn't ::Log managed)
|
||||
macro generate_log_line(message)
|
||||
{% if @def %}
|
||||
[ "Method({{@def.name}})", {{message}} ].join(" : ")
|
||||
{% else %}
|
||||
{{message}}
|
||||
{% end %}
|
||||
{% end %}
|
||||
end
|
||||
|
||||
{% for level in LOG_LEVELS.keys %}
|
||||
macro {{level.downcase.id}}(message, *args)
|
||||
::Logger.log({{level}}, Logger.generate_log_line + \{{message}}, \{{*args}})
|
||||
{% for level in %i[ trace debug info notice warn error fatal ].map(&.id) %}
|
||||
# Macro for running ::Log.{{level}} with message and splat args
|
||||
macro {{level.downcase}}(message, *args)
|
||||
log_with_context {{level}}, (\{{message}} \{% unless args.empty? %} % \{{args}} \{% end %})
|
||||
end
|
||||
{% end %}
|
||||
|
||||
protected def self.synchronize(&block : -> )
|
||||
LOG_MUTEX.synchronize do
|
||||
yield
|
||||
end
|
||||
end
|
||||
macro included
|
||||
{% if @type && ! @type.has_constant?("Log") %}
|
||||
Log = ::Log.for(self)
|
||||
{% end %}
|
||||
|
||||
def self.debug? : Bool
|
||||
LOG_MUTEX.synchronize do
|
||||
@@debug
|
||||
end
|
||||
end
|
||||
|
||||
def self.min=(selection : String) : Nil
|
||||
Logger.debug "Entering with #{selection}"
|
||||
raise "ERROR : {{level.id}}= : Invalid level" unless _level = LOG_LEVELS[selection.upcase]
|
||||
synchronize {
|
||||
@@min_level = _level
|
||||
}
|
||||
end
|
||||
|
||||
def self.min : String
|
||||
@@min_level.to_s.downcase
|
||||
end
|
||||
|
||||
def self.log_file=(@@log_file : String)
|
||||
end
|
||||
|
||||
def self.valid?(level : LOG_LEVEL) : Bool
|
||||
level.in?(@@min_level.value..@@max_level.value)
|
||||
end
|
||||
|
||||
def self.debug= (state : Bool) : Nil
|
||||
synchronize {
|
||||
@@debug = state
|
||||
}
|
||||
Logger.debug "Debug set to #{state}"
|
||||
end
|
||||
|
||||
def self.log(level : String, message : String, *args) : Nil
|
||||
_level = LOG_LEVELS[level]
|
||||
message = message % args unless args.empty?
|
||||
if valid?(_level) || debug?
|
||||
synchronize {
|
||||
time = Time.local
|
||||
message = [time, level.to_s.colorize(_level.color), message].join(" : ")
|
||||
|
||||
STDERR.puts message
|
||||
|
||||
unless @@log_file.empty?
|
||||
File.open(@@log_file, "a") do |file|
|
||||
file.puts message
|
||||
end
|
||||
end
|
||||
raise "ERROR : Fatal error detected (#{message})" if level == "FATAL"
|
||||
}
|
||||
end
|
||||
{% verbatim do %}
|
||||
macro finished
|
||||
{% defined_level_methods = [] of MacroId %}
|
||||
{% levels = %i[ trace debug info notice warn error fatal ] %}
|
||||
{% for level in levels %}
|
||||
{% defined_level_methods << level if @type.has_method? level %}
|
||||
{% end %}
|
||||
{% unless defined_level_methods.empty? %}
|
||||
{% raise "ERROR : You can't include ::Logger when you have the following defined as instance methods: #{defined_level_methods.join(", ").id}" %}
|
||||
{% end %}
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user