File: log_entry.rb

package info (click to toggle)
ruby-lumberjack 2.0.4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 956 kB
  • sloc: ruby: 7,957; makefile: 2
file content (214 lines) | stat: -rw-r--r-- 8,269 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# frozen_string_literal: true

module Lumberjack
  # A structured representation of a single log entry containing the message,
  # metadata, and contextual information. LogEntry objects are immutable data
  # structures that capture all relevant information about a logging event,
  # including timing, severity, source identification, and custom attributes.
  #
  # This class serves as the fundamental data structure passed between loggers,
  # formatters, and output devices throughout the Lumberjack logging pipeline.
  # Each entry maintains consistent structure while supporting flexible attribute
  # attachment for contextual logging scenarios.
  class LogEntry
    # @!attribute [rw] time
    #   @return [Time] The timestamp when the log entry was created
    # @!attribute [rw] message
    #   @return [String] The primary log message content
    # @!attribute [rw] severity
    #   @return [Integer] The numeric severity level of the log entry
    # @!attribute [rw] progname
    #   @return [String] The name of the program or component that generated the entry
    # @!attribute [rw] pid
    #   @return [Integer] The process ID of the logging process
    # @!attribute [rw] attributes
    #   @return [Hash<String, Object>] Custom attributes associated with the log entry
    attr_accessor :time, :message, :severity, :progname, :pid, :attributes

    TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%3N"

    # Create a new log entry with the specified components. The entry captures
    # all relevant information about a logging event in a structured format.
    #
    # @param time [Time] The timestamp when the log entry was created
    # @param severity [Integer, String, Symbol] The severity level, accepts numeric levels or labels
    # @param message [String] The primary log message content
    # @param progname [String, nil] The name of the program or component generating the entry
    # @param pid [Integer] The process ID of the logging process
    # @param attributes [Hash<String, Object>, nil] Custom attributes to associate with the entry
    def initialize(time, severity, message, progname, pid, attributes)
      @time = time
      @severity = (severity.is_a?(Integer) ? severity : Severity.label_to_level(severity))
      @message = message
      @progname = progname
      @pid = pid
      @attributes = flatten_attributes(attributes) if attributes.is_a?(Hash)
    end

    # Get the human-readable severity label corresponding to the numeric severity level.
    #
    # @return [String] The severity label (DEBUG, INFO, WARN, ERROR, FATAL, or UNKNOWN)
    def severity_label
      severity_data.label
    end

    # Get the data corresponding to the severity. This include variations on the severity label.
    def severity_data
      Severity.data(severity)
    end

    # Generate a formatted string representation of the log entry suitable for
    # human consumption. Includes timestamp, severity, program name, process ID,
    # attributes, and the main message.
    #
    # @return [String] A formatted string representation of the complete log entry
    def to_s
      msg = +"[#{time.strftime(TIME_FORMAT)} #{severity_label} #{progname}(#{pid})] #{message}"
      attributes&.each do |key, value|
        msg << " [#{key}:#{value}]"
      end
      msg
    end

    # Return a string representation suitable for debugging and inspection.
    #
    # @return [String] The same as {#to_s}
    def inspect
      to_s
    end

    # Compare this log entry with another for equality. Two log entries are
    # considered equal if all their components match exactly.
    #
    # @param other [Object] The object to compare against
    # @return [Boolean] True if the entries are identical, false otherwise
    def ==(other)
      return true if equal?(other)
      return false unless other.is_a?(LogEntry)

      time == other.time &&
        severity == other.severity &&
        message == other.message &&
        progname == other.progname &&
        pid == other.pid &&
        attributes == other.attributes
    end

    # Alias for tags to provide backward compatibility with version 1.x API. This method
    # will eventually be removed.
    #
    # @return [Hash, nil] The attributes of the log entry.
    # @deprecated Use {#attributes} instead.
    def tags
      Utils.deprecated("LogEntry#tags", "Lumberjack::LogEntry#tags is deprecated and will be removed in version 2.1; use attributes instead.") do
        attributes
      end
    end

    # Access an attribute value by name. Supports both simple and nested attribute
    # access using dot notation for hierarchical data structures.
    #
    # @param name [String, Symbol] The attribute name, supports dot notation for nested access
    # @return [Object, nil] The attribute value or nil if the attribute does not exist
    def [](name)
      return nil if attributes.nil?

      AttributesHelper.new(attributes)[name]
    end

    # Alias method for #[] to provide backward compatibility with version 1.x API. This
    # method will eventually be removed.
    #
    # @return [Hash]
    # @deprecated Use {#[]} instead.
    def tag(name)
      Utils.deprecated("LogEntry#tag", "Lumberjack::LogEntry#tag is deprecated and will be removed in version 2.1; use [] instead.") do
        self[name]
      end
    end

    # Expand flat attributes with dot notation into a nested hash structure.
    # Attributes containing dots in their names are converted into hierarchical
    # nested hashes for structured data representation.
    #
    # @return [Hash] The attributes expanded into a nested structure
    def nested_attributes
      Utils.expand_attributes(attributes)
    end

    # Alias for nested_attributes to provide API compatibility with version 1.x.
    # This method will eventually be removed.
    #
    # @return [Hash]
    # @deprecated Use {#nested_attributes} instead.
    def nested_tags
      Utils.deprecated("LogEntry#nested_tags", "Lumberjack::LogEntry#nested_tags is deprecated and will be removed in version 2.1; use nested_attributes instead.") do
        nested_attributes
      end
    end

    # Determine if the log entry contains no meaningful content. An entry is
    # considered empty if it has no message content and no attributes.
    #
    # @return [Boolean] True if the entry is empty, false otherwise
    def empty?
      (message.nil? || message == "") && (attributes.nil? || attributes.empty?)
    end

    # Convert the log entry into a hash suitable for JSON serialization. Attributes will be expanded
    # into a nested structure (i.e. { "user.id" => 123 } becomes `{ "user" => { "id" => 123 } }).
    # Severities will be converted to their string labels.
    #
    # @return [Hash] The JSON representation of the log entry
    def as_json
      {
        "time" => time,
        "severity" => severity_label,
        "message" => message,
        "progname" => progname,
        "pid" => pid,
        "attributes" => Utils.expand_attributes(attributes)
      }
    end

    private

    # Generate a string representation of all attributes for inclusion in the
    # formatted output. Each attribute is formatted as key:value pairs.
    #
    # @return [String] A formatted string of all attributes
    def attributes_to_s
      attributes_string = +""
      attributes&.each { |name, value| attributes_string << " #{name}:#{value.inspect}" }
      attributes_string
    end

    # Flatten nested attributes and remove empty values.
    #
    # @param attributes [Hash] The attributes hash to compact
    # @return [Hash] The flattened attributes with empty values removed
    def flatten_attributes(attributes)
      unless attributes.all? { |key, value| key.is_a?(String) && !value.is_a?(Hash) }
        attributes = Utils.flatten_attributes(attributes)
      end

      delete_keys = nil
      attributes.each do |key, value|
        if value.nil? || value == ""
          delete_keys ||= []
          delete_keys << key
        elsif value.is_a?(Array) && value.empty?
          delete_keys ||= []
          delete_keys << key
        end
      end

      return attributes if delete_keys.nil?

      attributes = attributes.dup
      delete_keys&.each { |key| attributes.delete(key) }

      attributes
    end
  end
end