From b4f00010c2fdbc92ab0ed3966b8b8b6fc367cbfa Mon Sep 17 00:00:00 2001 From: Tristan Ancelet Date: Mon, 31 Aug 2026 12:28:38 -0500 Subject: [PATCH] Moved the majority of the macros outside of the finished block. Un-needed --- src/logger.cr | 118 +++++++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/src/logger.cr b/src/logger.cr index 29ed373..2be266d 100644 --- a/src/logger.cr +++ b/src/logger.cr @@ -1,16 +1,70 @@ require "log" module Logger + + # 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 + + # 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 + + # Macro for doing the actual logging based on how the user has + # the Logger annotations + private 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 + + # Generates the log-line (everything that isn't ::Log managed) + private macro generate_log_line(message) + {% if @def %} + [ "Method({{@def.name}})", {{message}} ].join(" : ") + {% else %} + {{message}} + {% end %} + end + + {% 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 %} + macro included - annotation LogContext - end - - annotation LogMethod - end - - annotation LogConfig - end - {% if @type && ! @type.has_constant?("Log") %} Log = ::Log.for(self) {% end %} @@ -22,52 +76,10 @@ module Logger {% for level in levels %} {% defined_level_methods << level if @type.has_method? level %} {% end %} - {% raise "ERROR : You can't include ::Logger when you have the following defined as instance methods: #{defined_level_methods.join(", ").id}" unless defined_level_methods.empty? %} - end - - 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 %} - {% if @type.class? %} - {% class_def = @type.methods.includes?(@def) %} - {% if (vars = @type.instance_vars.select(&.annotation(LogContext))) && class_def %} - 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}} } + {% 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 - - macro generate_log_line(message) - {% if @def %} - [ "Method({{@def.name}})", {{message}}].join(" : ") - {% else %} - {{message}} - {% end %} - end - - {% for level in %i[ trace debug info notice warn error fatal ].map(&.id) %} - macro {{level.downcase}}(message, *args) - log_with_context {{level}}, (\{{message}} \{% unless args.empty? %} % \{{args}} \{% end %}) - end - {% end %} {% end %} end end