Swift Logging – NSHipster


In 2002,
the USA Congress enacted
the Sarbanes–Oxley Act,
which launched broad oversight to firms
in response to accounting scandals at firms like
Enron and
MCI WorldCom
round that point.
This act,

PCI

and


HIPAA

,
fashioned the regulatory backdrop
for a brand new era of
IT firms
rising from the dot-com bubble.

Across the identical time,
we noticed the emergence of ephemeral, distributed infrastructure —
what we now name “Cloud computing”
a paradigm that made techniques extra succesful but in addition extra complicated.

To unravel each the regulatory and logistical challenges of the 21st century,
our discipline established finest practices round software logging.
And most of the identical instruments and requirements are nonetheless in use as we speak.


This week on NSHipster,
we’re looking at
SwiftLog:
a community-driven, open-source customary for logging in Swift.

Developed by the Swift on Server neighborhood
and endorsed by the
SSWG (Swift Server Work Group),
its profit isn’t restricted to make use of on the server.
Certainly,
any Swift code meant to be run from the command line
would profit from adopting SwiftLog.
Learn on to learn the way.


As at all times,
an instance can be useful in guiding our dialogue.
Within the spirit of transparency and nostalgia,
let’s think about writing a Swift program
that audits the funds of a ’00s Fortune 500 firm.

import Basis

struct Auditor {
    func watch(_ listing: URL) throws {  }
    func cleanup() {  }
}

do {
    let auditor = Auditor()

    defer { auditor.cleanup() }
    attempt auditor.watch(listing: URL(string: "ftp:///reviews")!,
                     extensions: ["xls", "ods", "qdf"]) // ballot for modifications
} catch {
    print("error: (error)")
}

An Auditor sort polls for modifications to a listing
(an FTP server, as a result of keep in mind: it’s 2003).
Every time a file is added, eliminated, or modified,
its contents are audited for discrepancies.
If any monetary oddities are encountered,
they’re logged utilizing the print operate.
The identical goes for points connecting to the FTP,
or another issues this system would possibly encounter —
all the things’s logged utilizing print.

Easy sufficient.
We are able to run it from the command line like so:

$ swift run audit
beginning up...
ERROR: unable to reconnect to FTP

# (attempt once more after restarting PC beneath our desk)

$ swift run audit
+ linked to FTP server
! accounting discrepancy in stability sheet 
** Quicken database corruption! **
^C
shutting down...

Such a program is likely to be technically compliant,
but it surely leaves numerous room for enchancment:

  • For one,
    our output doesn’t have any timestamps related to it.
    There’s no strategy to know whether or not an issue was detected an hour in the past or final week.
  • One other downside is that our output lacks any coherent construction.
    At a look,
    there’s no easy strategy to isolate program noise from actual points.
  • Lastly, —
    and that is principally resulting from an under-specified instance
    it’s unclear how this output is dealt with.
    The place is that this output going?
    How is it collected, aggregated, and analyzed?

The excellent news is that
all of those issues (and plenty of others) could be solved
by adopting a proper logging infrastructure in your undertaking.


POSIX techniques,
applications function on three, predefined
streams:

File Deal with Description Title
0 stdin Customary Enter
1 stdout Customary Output
2 stderr Customary Error

By default,
Logger makes use of the built-in StreamLogHandler sort
to write down logged messages to straightforward output (stdout).
We are able to override this conduct to as an alternative write to straightforward error (stderr)
by utilizing the extra complicated initializer,
which takes a manufacturing facility parameter:
a closure that takes a single String parameter (the label)
and returns an object conforming to LogHandler.

let logger = Logger(label: "com.NSHipster.Auditor2000",
                    manufacturing facility: StreamLogHandler.customaryError)

12 Issue App ideas:

syslog messages —
and due to
this bundle by Ian Partridge,
Swift can, as nicely.

That stated,
few engineers have managed to retrieve this data
from the likes of Splunk
and lived to inform the story.
For us mere mortals,
we would want
this bundle by Will Lisac,
which sends log messages to
Slack.

The excellent news is that we will use each directly,
with out altering how messages are logged on the name website
by utilizing one other piece of the Logging module:
MultiplexLogHandler.

import struct Basis.Course ofData
import Logging
import LoggingSyslog
import LoggingSlack

LoggingSystem.bootstrap { label in
    let webhookURL = URL(string:
        Course ofData.course ofData.surroundings["SLACK_LOGGING_WEBHOOK_URL"]!
    )!
    var slackHandler = SlackLogHandler(label: label, webhookURL: webhookURL)
    slackHandler.logDegree = .essential

    let syslogHandler = SyslogLogHandler(label: label)

    return MultiplexLogHandler([
        syslogHandler,
        slackHandler
    ])
}

let logger = Logger(label: "com.NSHipster.Auditor2000")

With all of this in place,
our system will log all the things in syslog format to straightforward out (stdout),
the place it may be collected and analyzed by another system.


However the true power of this strategy to logging
is that it may be prolonged to satisfy the precise wants of any surroundings.
As an alternative of writing syslog to stdout or Slack messages,
your system might ship emails,
open SalesForce tickets,
or set off a webhook to activate some
IoT system.

Right here’s how one can lengthen SwiftLog to suit your wants
by writing a customized log handler:

customized handler
that codecs log messages for GitHub Actions
in order that they’re surfaced on GitHub’s UI like so:

When you’re curious about making your individual logging handler,
you possibly can study so much by simply shopping
the code for this undertaking.
However I did need to name out a number of factors of curiosity right here:

@testable import
of the module declaring GitHubActionsLogHandler,
we will entry that inner initializer from earlier than,
and move an occasion of MockTextual contentOutputStream to intercept logged messages.

import Logging
@testable import LoggingGitHubActions

ultimate class MockTextual contentOutputStream: Textual contentOutputStream {
    public personal(set) var strains: [String] = []

    public init(_ physique: (Logger) -> Void) {
        let logger = Logger(label: #file) { label in
            GitHubActionsLogHandler(outputStream: self)
        }

        physique(logger)
    }

    // MARK: - Textual contentOutputStream

    func write(_ string: String) {
        strains.append(string)
    }
}

With these items in place,
we will lastly check that our handler works as anticipated:

func checkLogging() {
    var logDegree: Logger.Degree?
    let expectation = MockTextual contentOutputStream { logger in
        logDegree = logger.handler.logDegree

        logger.hint("🥱")
        logger.error("😱")
    }

    XCTAssertLargerThan(logDegree!, .hint)
    XCTAssertEqual(expectation.strains.rely, 1) // hint log is ignored
    XCTAssertTrue(expectation.strains[0].hasPrefix("::error "))
    XCTAssertTrue(expectation.strains[0].hasSuffix("::😱"))
}

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles