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.
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.
Using Xcode
- File > Add Package Dependencies...
- 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
- add this to your
dependencies
list
.package(url: "https://github.com/oldgrowth-games/SumoLogHandler", from: "4.0.0")
- add
"SumoLogHandler"
to your list target dependencies
.target(name: "SumoLogHandler", dependencies: ["SumoLogHandler"]),
- Product > Scheme > Edit Scheme
- Click
Run
on the left, thenArguments
on the top - Under
Environment Variables
, click the+
button - Set the
Name
toSUMO_URL
- Set the
Value
to the url you got in theSumoLogic Setup
step above, but without thehttps://
- This is very important! If you leave the
https://
in the value, it will not be parsed properly.
- This is very important! If you leave the
- Add
SUMO_URL
to your CI variables as well (again without thehttps://
).
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 sameName
andValue
(again without thehttps://
). - Update
ci_scripts/ci_post_clone.sh
withplutil -replace SUMO_URL -string $SUMO_URL Info.plist
- (See Notes at the bottom of this README for more details about ci_scripts)
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"])
}
}
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"
}
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
⚫️ [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()]
⚪️ [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]
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