Skip to content

Latest commit

 

History

History
402 lines (312 loc) · 15.4 KB

Transports.md

File metadata and controls

402 lines (312 loc) · 15.4 KB

Transports

Introduction

A transport is essentially a storage device for your logs.
Each logger can have multiple transports configured at different levels; you can also customize how the messages are formatted.

Writing log messages to various locations is an essential feature of any robust logging library.
This is made possible in Glider through the Transport protocol:

public protocol Transport { 
    var queue: DispatchQueue { get }
    var isEnabled: Bool { get set }
    var minimumAcceptedLevel: Level? { get set }

    @discardableResult
    func record(event: Event) -> Bool
}
  • queue: return the GCD queue that will be used when executing tasks related to the receiver. Log formatting and recording will be performed using this queue. A serial queue is typically used, such as when the underlying log facility is inherently single-threaded and/or proper message ordering wouldn't be ensured otherwise. However, a concurrent queue may also be used, and might be appropriate when logging to databases or network endpoints.
  • isEnabled: allows to temporary disable a transport without disabling its parent Log.
  • minimumAcceptedLevel: A filter by severity implemented at the transport level. You can, for example, create a logger which logs in info but for one of the transport (for example ELK or Sentry) it avoids to send messages with a severity lower than error in order to clog your remote service). When nil the message is not filtered and all messages accepted by the parent Log instance are accepted automatically.

Note Moreover, most transports also specify another common property called formatters. A Formatter is an object that conforms to EventFormatter protocol, which allows you to format the Event's message and transform them as you need.
For example, ConsoleTransport defines a single formatter used to print event messages in a style similar to a common XCode print statement (with a timestamp, PID, message, etc.).

The most important function of the protocol is the record(event:) function which receives Event instances coming from the parent Log instance and implements its own logic to store/send values.

Apple Swift-Log Integration

Glider can also work as a backend for apple/swift-log.

The GliderSwiftLogHandler offers a LogHandler object which you can assign to the swift-log settings to use Glider as the backend:

LoggingSystem.bootstrap {
    var handler = GliderSwiftLogHandler(label: loggerName, logger: gliderLogger)
    handler.logLevel = .trace
    return handler
}

Glider for Apple Swift-Log is available as separate plugin in Glider-AppleSwiftLog repository.

Base Transports

Glider offers several base transport layers you can use to simplify the creation of your own transport.

AsyncTransport

AsyncTransport is a transport specifically made for asynchronous requests.
It stores logs locally in a temporary (in-memory) SQLite3 database; once a specified amount of messages are collected it allows to send them via a network request.

Note Typically, you don't need to use AsyncTransport as is. It powers the HTTPTransport transport service.

let config = AsyncTransport.Configuration {
    $0.autoFlushInterval = 3 // periodic flush interval called even if chunk size is not reached
    $0.bufferStorage = .inMemory // where the messages are temporary saved
    $0.chunksSize = 20 // how much events must contain each chunk at max
}
let transport = try AsyncTransport(delegate: self, configuration: config)

// The main message of the delegate
public func asyncTransport(_ transport: AsyncTransport,
                               canSendPayloadsChunk chunk: AsyncTransport.Chunk,
                               onCompleteSendTask completion: @escaping ((ChunkCompletionResult) -> Void)) {
    // Do something with the chunk of events collected
}

BufferedTransport

The BufferedTransport is a generic event recorder that buffers the log messages passed to its record(event:) function. Construction requires a bufferedItemBuilder function, which is responsible for converting the event and formatted message SerializableData into the generic BufferItem type.

ThrottledTransport

The throttled transport is a tiny but thread-safe logger with a buffering and retrying mechanism for iOS. Buffer is a limit cap; when reached, call the flush mechanism.
You can also set a time interval to auto flush the content of the buffer regardless the number of currently stored payloads.

Note Typically, you don't need to use ThrottledTransport as is. It powers the SQLiteTransport transport service.

Console Formatters

ConsoleTransport

ConsoleTransport is used to print logs directly on Xcode or other IDE consoles. By default, when initialized, the console transport is initialized by setting the XCodeFormatter an event message formatter used to print messages using the similar output of standard NSLog() or print() methods.

// This creates a custom configuration of the `XCodeFormatter`, which print
// print colored warning/error messages.
// NOTE: Xcode does not support colored console anymore, so you need to install
// this font:
// https://raw.githubusercontent.com/jjrscott/ColoredConsole/master/ColoredConsole-Bold.ttf
// And set it as the font for Console fonts inside the settings panel of XCode.
let consoleTransport = ConsoleTransport {
    $0.minimumAcceptedLevel = .info // print only info or more severe messages even if the log specify .trace below
    // setup formatters
    $0.formatters = [
        XCodeFormatter(showCallSite: false, colorize: .onlyImportant)
    ]
}

let logger = Log {
    $0.level = .trace
    $0.transports = [consoleTransport]
}

OSLogTransport

Note Available on macOS, iOS and tvOS. Use POSIXTransport on Linux.

The OSLogTransport is an implemention of the Transport protocol that records log entries using the new unified logging system available s of iOS 10.0, macOS 10.12, tvOS 10.0.

More informations here.

let osLogTransport = OSLogTransport {
    $0.formatters = [SysLogFormatter()] // change the default formatter
    $0.levelTranslator = .strict // set strict mapping between glider's level and syslog
}

let logger = Log {
    $0.level = .trace
    $0.transports = [osLogTransport]
}

POSIXStreamTransport

This transport can output text messages to POSIX stream.

// Dispatch messages to the std-out stream using the TerminalFormatter to format the output text.
let logger = Log {
    $0.level = .trace
    $0.transports = [
        StdStreamsTransport.stdOut(formatters: [TerminalFormatter()])
    ]
}

File Formatters

FileTransport

A FileTransport implementation that appends log entries to a local file. FileTransport is a simple log appender that provides no mechanism for file rotation or truncation.

Unless you manually manage the log file when a FileTransport doesn't have it open, you will end up with an ever-growing file.

Note Use a SizeRotationFileTransport instead if you'd rather not have to concern yourself with such details.

// Create a trasnport to save a json formatted version of received events.
let fileURL = URL.temporaryFileURL()
let fileTransport = try FileTransport(fileURL: fileURL, {
    $0.formatters = [ JSONFormatter.standard() ] // output format as JSON payloads
})
        
let logger = Log {
    $0.level = .trace
    $0.transports = [fileTransport]
}

SizeRotationFileTransport

SizeRotationFileTransport is the evolution of FileTransport: it maintains a set of daily rotating log files, kept for a user-specified number of days.
SizeRotationFileTransport instance assumes full control over the log directory specified by its directoryPath property.

// Create a rotated files transport. It manages a directory where
// a maximum of 4 files of logs (500kb each) rotates by removing the
// oldest events.
// Events are saved in JSON format.
let directoryURL = try URL.newDirectoryURL()
let rotateFilesTransport = try SizeRotationFileTransport(directoryURL: directoryURL) {
    $0.maxFileSize = kilobytes(500) // maximum size per file
    $0.maxFilesCount = 4 // max number of logs
    $0.filePrefix = "mylog_" // custom file name
    $0.formatters = [JSONFormatter.standard()] // output format for events
}
        
let logger = Log {
    $0.level = .trace
    $0.transports = [rotateFilesTransport]
}

SQLiteTransport

SQLiteTransport offers the ability to store events in a compact, searchable local sqlite3 database.
We strongly suggest using this database when you need to collect the relevant amount of data; it offers great reliability, and it's fast.

// create an local database at given url
let sqliteTransport = try SQLiteTransport(databaseLocation: .fileURL(url), {
    // this transport used the ThrottledTransport as helper in order to optimize
    // how the events are stored (we would avoid creating a SQL transaction per each
    // event, so we connect enough data before making a single atomic transaction).
    $0.throttledTransport = .init({
        // Size of the buffer.
        // Keep in mind: a big size may impact to the memory. 
        // Tiny sizes may impact on storage service load.
        $0.maxEntries = 100
        // if not enough events (maxEntries) are collected in this interval do a transaction and flush data.
        $0.autoFlushInterval = 5
    })
    $0.delegate = self // listen for events
})
        
let logger = Log {
    $0.level = .trace
    $0.transports = [sqliteTransport]
}                

Remote Formatters

HTTPTransport

The HTTPTransport is used to send log events directly to an HTTP service by executing network calls to a specific endpoint.

It's up to the delegate (HTTPTransportDelegate) to produce a list of HTTTransportRequest requests which will be then executed and handled automatically by the transport.
It also supports a retry mechanism in case of network errors.

let transport = try HTTPTransport(delegate: self) {
    $0.maxConcurrentRequests = 3 // 3 concurrent network request at max
    $0.formatters = [SysLogFormatter()] // setup the format of output to syslog
    $0.maxEntries = 100 // maximum number of events storable (LIFO)
    $0.chunkSize = 5 // number of events per each request
    $0.autoFlushInterval = 5 // auto flush interval each 5 seconds
}
transport.delegate = self

let log = Log {
    $0.transports = [transport]
    $0.level = .trace
}

The HTTPTransportDelegate should implement at least the method to produce the URLRequest used to send data to a remote web service:

 // MARK: - HTTPTransportDelegate
    
func httpTransport(_ transport: HTTPTransport,
                   prepareURLRequestsForChunk chunk: AsyncTransport.Chunk) -> [HTTPTransportRequest] {
        
    chunk.map { event, message, attempt in
        var urlRequest = URLRequest(url: URL(string: "http://myendoint:10450/receive.php")!)
        urlRequest.httpBody = message?.asData()
        urlRequest.httpMethod = "POST"
        urlRequest.timeoutInterval = 10
        return HTTPTransportRequest(urlRequest: urlRequest) {
            $0.maxRetries = 2 // 2 maximum retry on connection errors
        }
    }
}

RemoteTransport

Note Currently not available under Linux

The RemoteTransport is used to send log in a custom binary format to a LAN/WAN destination. It uses Bonjour/ZeroConfig to found active server where to send data.

Warning Be sure to set the following keys in your app's Info.plist:

<key>NSLocalNetworkUsageDescription</key>
   <string>Network usage required for debugging activities</string>
<key>NSBonjourServices</key>
<array>
   <string>_glider._tcp</string>
</array>

Example:

let remoteTransport = try RemoteTransport(serviceType: self.serviceType, delegate: nil, {
    $0.autoConnectServerName = serverName // automatically search & connect to this server name
})

let logger = Log {
    $0.transports = [remoteTransport]
}

Note We suggest to use a single shared instance of this transport for all of yours loggers. In this case use the RemoteTransport.shared shortcut instead of creating a new one.

RemoteTransportServer

Gliders also offer a server so you can easily capture client connections from RemoteTransport instance:

let server = RemoteTransportServer(serviceName: serverName, serviceType: serviceType, delegate: self)
try self.server?.start()

Then you can use the RemoteTransportServerDelegate in order to receive events from connected clients:

func remoteTransportServer(_ server: RemoteTransportServer,
                           client: RemoteTransportServer.Client,
                           didReceiveEvent event: Event) {
    print("New event received from client: \(event.message)")
}

WebSocketTransport

Note Currently not available under Linux

WebSocketTransportClient

The WebSocketTransportClient is used to transport messages to a WebSocket compliant server. Each message is transmitted to the server directly on the record.

Note In order to optimize message transmission, we strongly suggest using a binary format like MsgPackFormatter.

  // We are using a custom textual format for message output.
  // It just includes the timestamp, severity and message.
  // (note: we're not sending attached `object` or `extra`/`tags` data, neither other context).
let customFormat = FieldsFormatter(fields: [
    .timestamp(),
    .message({
        $0.truncate = .head(length: 10)
    }),
])

let wsTransport = try WebSocketTransportClient(url: "ws://localhost:1011", delegate: self) {
    $0.connectAutomatically = true
    $0.formatters = [customFormat]
    $0.dataType = .event(encoder: JSONEncoder())
}
                
let logger = Log {
    $0.level = .trace // send any message, including low priority events like `trace` or `info`.
    $0.transports = [wsTransport]
}

WebSocketTransportServer

You can also create a server and send events directly to any connected client. Just create a WebSocketTransportServer transport:

    let transport = try WebSocketTransportServer(port: port, delegate: self, {
    $0.startImmediately = true
    $0.formatters = [customFormat]
})

and use delegate with WebSocketTransportServerDelegate to listen for useful events coming from the server (connection and/or disconnection by clients or any other error).