Skip to content

A custom LogHandler for apple/swift-log that sends messages to SumoLogic.

License

Notifications You must be signed in to change notification settings

vlaminck/SumoLogHandler

 
 

Repository files navigation

SumoLogHandler

codecov

A custom LogHandler for apple/swift-log that sends messages to SumoLogic. It does not print to the console so you can use whatever LogHandler you want for printing to the console. StreamLogHandler.standardOutput works pretty well, but SumoLogHandler also includes EmojiConsolePrinter which prefixes each message with an Emoji that represents the log level.

SumoLogic Setup

You'll need to set up a new collector in sumo. Here's more information about that https://help.sumologic.com/Manage/Collection

I named mine "Mobile" and set the Source Category to "prod/mobile". This is just the default that Sumo uses, but SumoTree will overwrite it with whatever you set for sumoCategory which is defaulted to "prod/mobile". This is customizable at any time so you can adjust it to "dev/mobile", "preprod/mobile", or whatever you name your environments.

Copy the url for your collector. If you didn't set it up, this can be found in Manage Data > Collection. Then to the right of your collector (Mine is named "Mobile"), click Show URL, then click the Copy button. Hang on to this url. You'll want to add it to your scheme for testing, and to your CI variables for TestFlight and App Store builds.

Application Setup

1. Add SumoLogHandler to your project

Using Xcode
  1. File > Add Package Dependencies...
  2. Paste the url for this project in search bar in the top-right corner
https://github.com/oldgrowth-games/SumoLogHandler
Using a `package.swift` file
  1. add this to your dependencies list
.package(url: "https://github.com/oldgrowth-games/SumoLogHandler", from: "4.0.0")
  1. add "SumoLogHandler" to your list target dependencies
.target(name: "SumoLogHandler", dependencies: ["SumoLogHandler"]),

2. Add SUMO_URL to your scheme and CI variables

  1. Product > Scheme > Edit Scheme
  2. Click Run on the left, then Arguments on the top
  3. Under Environment Variables, click the + button
  4. Set the Name to SUMO_URL
  5. Set the Value to the url you got in the SumoLogic Setup step above, but without the https://
    • This is very important! If you leave the https:// in the value, it will not be parsed properly.
  6. Add SUMO_URL to your CI variables as well (again without the https://).
Xcode Cloud steps here
  • Xcode Cloud Variables can be found under Integrate > Manage Workflows
  • Open your workflow, and select Environment on the left
  • Add a new Environment Variable with the same Name and Value (again without the https://).
  • Update ci_scripts/ci_post_clone.sh with plutil -replace SUMO_URL -string $SUMO_URL Info.plist
    • (See Notes at the bottom of this README for more details about ci_scripts)

3. Bootstrap your Loggers

In your App file, bootstrap an instance of SumoLogHandler along with any others you want to use.

import SwiftUI
import Logging
import SumoLogHandler

public var logger: Logger = Logger(label: "com.mycompany.mysweetapp")

@main
struct MySweetApp: App {
  
  init() {
    bootstrapLogger()
  }

  var body: some Scene {
    ...
  }

  private func bootstrapLogger() {
    if
      let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String,
      let sumoUrlString = ProcessInfo.processInfo.environment["SUMO_URL"],
      let sumoUrl = URL(string: "https://\(sumoUrlString)")
    {
      LoggingSystem.bootstrap { label in
        MultiplexLogHandler(
          [
            SumoLogHandler(
              label: label,
              sumoUrl: sumoUrl,
              sourceName: appName,
              systemName: UIDevice.current.systemName,
              systemVersion: UIDevice.current.systemVersion
            ),
            EmojiConsolePrinter(label: label) // StreamLogHandler.standardOutput(label: label) // if you don't want nice console printing
          ]
        )
      }
    } else {
      LoggingSystem.bootstrap { label in
        EmojiConsolePrinter(label: label) // StreamLogHandler.standardOutput(label: label) // if you don't want nice console printing
      }
    }
    
    // now that we've registered log handlers, we can set logLevel and other metadata.
    // logger.logLevel just forwards to the handlers so we need to do this after registering our handlers
#if DEBUG
    logger.logLevel = .trace
#else
    logger.logLevel = .info
#endif
    
    // any metadata you add to the logger will be sent along to Sumo with every log message
    if let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
      logger[metadataKey: "appVersion"] = .string(appVersion)
    }
    if let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
      logger[metadataKey: "buildNumber"] = .string(buildNumber)
    }
    
    // If you pass metadata to a single log message, it will be passed to sumo for that message only
    logger.info("this is the only message with foo:bar metadata", metadata: ["foo": "bar"])

  }
  
}

Usage

Because SumoLogHandler uses apple/swift-log, all you have to do is call the globally accessible logger that you set up in the previous step.

logger.trace("trace message")
logger.debug("debug message")
logger.info("info message")
logger.notice("notice message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

You can then query SumoLogic with things like

_sourceCategory="prod/mobile"
_sourceHost="ios"
_sourceName="sweet-project-name"

The log messages will look something like this

{
  "lineNumber":"84",
  "appVersion":"2.0",
  "function":"bootstrapLogger()",
  "fileName":"MySweetApp",
  "timestamp":"2023-11-22T10:32:35.315-0600",
  "foo":"bar",
  "message":"this is the only message with foo:bar metadata",
  "buildNumber":"1",
  "systemVersion":"17.0.1",
  "systemName":"iOS",
  "machine":"arm64",
  "logLevel":"info"
}

Notes

SumoLogic takes time to parse your logs, especially the first time you set it up. You can verify that SumoLogHandler is uploading successfully, by passing enableConsolePrinting: true in the SumoLogHandler.init. Passing enableConsolePrinting: true tells SumoLogHandler to print things about how it's functioning. These do not use the swift-log functions, they only print. This is useful for developing SumoLogHandler, and for verifying that uploads are succeeding. Beyond that, it's just noise in your console so you should leave it off most of the time.

EmojiConsolePrinter details in here

EmojiConsolePrinter has the following options

  • printFileFunctionAndLine: Bool
  • printMetadata: Bool

printFileFunctionAndLine defaults to true and looks like this when true

⚫️ [INFO]     this is the only message with foo:bar metadata [the_one_exampleApp:79 bootstrapLogger()]
⚪️ [TRACE]    trace message [the_one_exampleApp:82 bootstrapLogger()]
⚪️ [DEBUG]    debug message [the_one_exampleApp:83 bootstrapLogger()]
⚫️ [INFO]     info message [the_one_exampleApp:84 bootstrapLogger()]
🟡 [NOTICE]   notice message [the_one_exampleApp:85 bootstrapLogger()]
🟠 [WARNING]  warning message [the_one_exampleApp:86 bootstrapLogger()]
🔴 [ERROR]    error message [the_one_exampleApp:87 bootstrapLogger()]

printMetadata defaults to false and looks like this when true

⚪️ [TRACE]    trace message [the_one_exampleApp:82 bootstrapLogger()] ["appVersion": 1.0, "buildNumber": 1]
⚪️ [DEBUG]    debug message [the_one_exampleApp:83 bootstrapLogger()] ["appVersion": 1.0, "buildNumber": 1]
⚫️ [INFO]     info message [the_one_exampleApp:84 bootstrapLogger()] ["appVersion": 1.0, "buildNumber": 1]
🟡 [NOTICE]   notice message [the_one_exampleApp:85 bootstrapLogger()] ["appVersion": 1.0, "buildNumber": 1]
🟠 [WARNING]  warning message [the_one_exampleApp:86 bootstrapLogger()] ["appVersion": 1.0, "buildNumber": 1]
🔴 [ERROR]    error message [the_one_exampleApp:87 bootstrapLogger()] ["appVersion": 1.0, "buildNumber": 1]

StreamLogHandler.standardOutput(label: label) looks like this, BTW

2025-02-15T11:37:24-0600 trace the-one-example : appVersion=1.0 buildNumber=1 [the_one_example] trace message
2025-02-15T11:37:24-0600 debug the-one-example : appVersion=1.0 buildNumber=1 [the_one_example] debug message
2025-02-15T11:37:24-0600 info the-one-example : appVersion=1.0 buildNumber=1 [the_one_example] info message
2025-02-15T11:37:24-0600 notice the-one-example : appVersion=1.0 buildNumber=1 [the_one_example] notice message
2025-02-15T11:37:24-0600 warning the-one-example : appVersion=1.0 buildNumber=1 [the_one_example] warning message
2025-02-15T11:37:24-0600 error the-one-example : appVersion=1.0 buildNumber=1 [the_one_example] error message
Reminder about Xcode Cloud environment variables in ci_scripts/ci_post_clone.sh

In order to move environment variables into your app during the Xcode Cloud build, you need to manually do it in ci_scripts/ci_post_clone.sh like this

#!/bin/sh

#  ci_post_clone.sh
#
# ci variable reference
# https://developer.apple.com/documentation/xcode/environment-variable-reference
#
# This is where Apple explains where this build script is executed
# https://developer.apple.com/documentation/xcode/writing-custom-build-scripts#Create-a-custom-build-script


echo "ci_post_clone.sh start"

if [[ -n $CI_PULL_REQUEST_NUMBER ]];
    echo "This build did not start from a pull request."
then
    echo "This build started from a pull request."
    # Perform an action only if the build starts from a pull request.
fi

# move into the project directory
cd ../<YourRootFolderName>/

# inject environment variables into Info.plist
plutil -replace SUMO_URL -string $SUMO_URL Info.plist

plutil -p Info.plist

echo "ci_post_clone.sh end"
exit 0

About

A custom LogHandler for apple/swift-log that sends messages to SumoLogic.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 100.0%