File: device.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 (176 lines) | stat: -rw-r--r-- 7,313 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
# frozen_string_literal: true

require_relative "device_registry"

module Lumberjack
  # Abstract base class defining the interface for logging output devices.
  # Devices are responsible for the final output of log entries to various
  # destinations such as files, streams, databases, or external services.
  #
  # This class establishes the contract that all concrete device implementations
  # must follow, with the +write+ method being the only required implementation.
  # Additional lifecycle methods (+close+, +flush+, +reopen+) and configuration
  # methods (+datetime_format+) are optional but provide standardized interfaces
  # for device management.
  #
  # The device architecture allows for flexible log output handling while
  # maintaining consistent behavior across different output destinations.
  # Devices receive formatted LogEntry objects and are responsible for their
  # final serialization and delivery.
  #
  # @abstract Subclass and implement {#write} to create a concrete device
  # @see Lumberjack::Device::Writer File-based output device
  # @see Lumberjack::Device::LoggerWrapper Ruby Logger compatibility device
  # @see Lumberjack::Device::Multi Multiple device routing
  # @see Lumberjack::Device::Null Silent device for testing
  # @see Lumberjack::Device::Test In-memory device for testing
  class Device
    require_relative "device/writer"
    require_relative "device/log_file"
    require_relative "device/logger_wrapper"
    require_relative "device/multi"
    require_relative "device/null"
    require_relative "device/test"
    require_relative "device/buffer"
    require_relative "device/size_rolling_log_file"
    require_relative "device/date_rolling_log_file"

    class << self
      # Open a logging device with the given options.
      #
      # @param device [nil, Symbol, String, File, IO, Array, Lumberjack::Device, ContextLogger] The device to open.
      #   The device can be:
      #   - +nil+: returns a +Device::Null+ instance that discards all log entries.
      #   - +Symbol+: looks up the device in the +DeviceRegistry+ and creates a new instance with the provided options.
      #   - +String+ or +Pathname+: treated as a file path and opens a +Device::LogFile+.
      #   - +File+: opens a +Device::LogFile+ for the given file stream.
      #   - +IO+: opens a +Device::Writer+ wrapping the given IO stream.
      #   - +Lumberjack::Device+: returns the device instance as-is.
      #   - +ContextLogger+: wraps the logger in a +Device::LoggerWrapper+.
      #   - +Array+: each element is treated as a device specification and opened recursively,
      #     returning a +Device::Multi+ that routes log entries to all specified devices. Each
      #     device can have its own options hash if passed as a two-element array +[device, options]+.
      # @param options [Hash] Options to pass to the device constructor.
      # @return [Lumberjack::Device] The opened device instance.
      #
      # @example Open a file-based device
      #   device = Lumberjack::Device.open_device("/var/log/myapp.log", shift_age: "daily")
      #
      # @example Open a stream-based device
      #   device = Lumberjack::Device.open_device($stdout)
      #
      # @example Open a device from the registry
      #   device = Lumberjack::Device.open_device(:syslog)
      #
      # @example Open multiple devices
      #   device = Lumberjack::Device.open_device([["/var/log/app.log", {shift_age: "daily"}], $stdout])
      #
      # @example Wrap another logger
      #   device = Lumberjack::Device.open_device(Lumberjack::Logger.new($stdout))
      def open_device(device, options = {})
        device = device.to_s if device.is_a?(Pathname)

        if device.nil?
          Device::Null.new
        elsif device.is_a?(Device)
          device
        elsif device.is_a?(Symbol)
          DeviceRegistry.new_device(device, options)
        elsif device.is_a?(ContextLogger) || device.is_a?(::Logger)
          Device::LoggerWrapper.new(device)
        elsif device.is_a?(Array)
          devices = device.collect do |dev, dev_options|
            dev_options = dev_options.is_a?(Hash) ? options.merge(dev_options) : options
            open_device(dev, dev_options)
          end
          Device::Multi.new(devices)
        elsif io_but_not_file_stream?(device)
          Device::Writer.new(device, options)
        else
          Device::LogFile.new(device, options)
        end
      end

      private

      def io_but_not_file_stream?(object)
        return false if object.is_a?(File)
        return false unless object.respond_to?(:write)
        return true if object.respond_to?(:tty?) && object.tty?
        return false if object.respond_to?(:path) && object.path

        true
      end
    end

    # Write a log entry to the device. This is the core method that all device
    # implementations must provide. The method receives a fully formatted
    # LogEntry object and is responsible for outputting it to the target
    # destination.
    #
    # @param entry [Lumberjack::LogEntry] The log entry to write to the device
    # @return [void]
    # @abstract Subclasses must implement this method
    # @raise [NotImplementedError] If called on the abstract base class
    def write(entry)
      raise NotImplementedError
    end

    # Close the device and release any resources. The default implementation
    # calls flush to ensure any buffered data is written before closing.
    # Subclasses should override this method if they need to perform specific
    # cleanup operations such as closing file handles or network connections.
    #
    # @return [void]
    def close
      flush
    end

    # Reopen the device, optionally with a new log destination. The default
    # implementation calls flush to ensure data consistency. This method is
    # typically used for log rotation scenarios or when changing output
    # destinations dynamically.
    #
    # @param logdev [Object, nil] Optional new log device or destination
    # @return [void]
    def reopen(logdev = nil)
      flush
    end

    # Flush any buffered data to the output destination. The default
    # implementation is a no-op since not all devices use buffering.
    # Subclasses that implement buffering should override this method
    # to ensure data is written to the final destination.
    #
    # @return [void]
    def flush
    end

    # Get the current datetime format string used for timestamp formatting.
    # The default implementation returns nil, indicating no specific format
    # is set. Subclasses may override this to provide device-specific
    # timestamp formatting.
    #
    # @return [String, nil] The datetime format string, or nil if not set
    def datetime_format
    end

    # Set the datetime format string for timestamp formatting. The default
    # implementation is a no-op. Subclasses that support configurable
    # timestamp formatting should override this method to store and apply
    # the specified format.
    #
    # @param format [String, nil] The datetime format string to use for timestamps
    # @return [void]
    def datetime_format=(format)
    end

    # Expose the underlying stream if any.
    #
    # @return [IO, Lumberjacke::Device, nil]
    # @api private
    def dev
      self
    end
  end
end