Module: Axn::Internal::CallLogger

Extended by:
CallLogger
Included in:
CallLogger
Defined in:
lib/axn/internal/call_logger.rb

Overview

Logs action execution - handles building and emitting structured log messages for action calls with context formatting and truncation.

Constant Summary collapse

MAX_CONTEXT_LENGTH =
150
TRUNCATION_SUFFIX =
"…<truncated>…"

Instance Method Summary collapse

Instance Method Details

#log_at_level(action_class, level:, message_parts:, error_context:, join_string: " ", before: nil, after: nil, prefix: nil, context_direction: nil, context_instance: nil, context_data: nil, facets: nil) ⇒ Object

Logs a message at the specified level with error handling

Parameters:

  • action_class (Class)

    The action class to log from

  • level (Symbol)

    The log level (e.g., :info, :warn)

  • message_parts (Array<String>)

    Parts of the message to join

  • error_context (String)

    Context for error reporting if logging fails

  • join_string (String) (defaults to: " ")

    String to join message parts with

  • before (String, nil) (defaults to: nil)

    Text to prepend to the message

  • after (String, nil) (defaults to: nil)

    Text to append to the message

  • prefix (String, nil) (defaults to: nil)

    Override the default log prefix (useful for class-level logging)

  • context_direction (Symbol, nil) (defaults to: nil)

    Direction for context logging (:inbound or :outbound)

  • context_instance (Object, nil) (defaults to: nil)

    Action instance for instance-level context_for_logging

  • context_data (Hash, nil) (defaults to: nil)

    Raw data for class-level context_for_logging

  • facets (Hash, nil) (defaults to: nil)

    Resolved observability facets ({ tags:, dimensions: }) to annotate the line with



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/axn/internal/call_logger.rb', line 34

def log_at_level( # rubocop:disable Metrics/ParameterLists
  action_class,
  level:,
  message_parts:,
  error_context:,
  join_string: " ",
  before: nil,
  after: nil,
  prefix: nil,
  context_direction: nil,
  context_instance: nil,
  context_data: nil,
  facets: nil
)
  return unless level

  Axn::Extensions.best_effort(error_context, action: action_class) do
    # Prepare and format context if needed
    context_str = if context_instance && context_direction
                    # Instance-level: use private inputs_for_logging / outputs_for_logging
                    data = case context_direction
                           when :inbound then context_instance.send(:inputs_for_logging)
                           when :outbound then context_instance.send(:outputs_for_logging)
                           end
                    format_context(data)
                  elsif context_data && context_direction
                    # Class-level: use internal _context_slice
                    data = action_class._context_slice(data: context_data, direction: context_direction)
                    format_context(data)
                  end

    # Add context to message parts if present
    full_message_parts = context_str ? message_parts + [context_str] : message_parts
    message = full_message_parts.compact.join(join_string)

    # Annotate with resolved tag/dimension facets: structured named tags when the configured
    # logger is a SemanticLogger (legible as log fields / Datadog facets), otherwise a readable
    # suffix on the plain line. Mutually exclusive — semantic_logger's own formatter renders the
    # named tags, so the suffix would be redundant there.
    named_tags = facets ? Axn::Core::Tagging.namespaced(tags: facets[:tags] || {}, dimensions: facets[:dimensions] || {}) : {}

    if named_tags.any? && semantic_logger?
      SemanticLogger.tagged(**named_tags) do
        action_class.public_send(level, message, before:, after:, prefix:)
      end
    else
      message += facet_suffix(facets) if named_tags.any?
      action_class.public_send(level, message, before:, after:, prefix:)
    end
  end
end

#semantic_logger?Boolean

True only when the logger that will actually emit is a SemanticLogger — merely having the gem loaded isn't enough, since its thread-local tagged context is read only by SemanticLogger instances (see design: gating on the configured logger avoids facets vanishing from a line emitted by a plain Logger). Public so the Executor can gate the in-flight body context on it.

Returns:

  • (Boolean)


90
91
92
# File 'lib/axn/internal/call_logger.rb', line 90

def semantic_logger?
  defined?(SemanticLogger::Logger) && Axn.config.logger.is_a?(SemanticLogger::Logger)
end