-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add first implementation of Sentry logger
- Loading branch information
1 parent
d82d0fc
commit 74b1c44
Showing
10 changed files
with
291 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import Foundation | ||
import Logging | ||
|
||
public class SentryFormatter: LogFormatter { | ||
public var metadata = Logger.Metadata() { | ||
didSet { | ||
self.prettyMetadata = self.prettify(self.metadata) | ||
} | ||
} | ||
|
||
private var prettyMetadata: String? | ||
private let jsonEncoder: JSONEncoder | ||
private let newLine = "\n".data(using: .utf8)! | ||
|
||
public init() { | ||
self.jsonEncoder = JSONEncoder() | ||
self.jsonEncoder.dateEncodingStrategy = JSONEncoder.DateEncodingStrategy.iso8601 | ||
} | ||
|
||
public func format(label: String, | ||
level: Logger.Level, | ||
message: Logger.Message, | ||
metadata: Logger.Metadata?, | ||
file: String, | ||
function: String, | ||
line: UInt) throws -> Data? { | ||
|
||
// TODO: Save metadata as additional fields. | ||
// let prettyMetadata = metadata?.isEmpty ?? true | ||
// ? self.prettyMetadata | ||
// : self.prettify(self.metadata.merging(metadata!, uniquingKeysWith: { _, new in new })) | ||
// let messageLine = "(\(prettyMetadata.map { ", \($0)" } ?? "")): \(message)\n" | ||
|
||
let eventId = UUID().uuidString // self.createRandomString(length: 32) | ||
let currentDate = Date() | ||
|
||
let header = EnvelopeHeader(eventId: eventId, | ||
sentAt: currentDate, | ||
sdk: EnvelopeSdk(name: "dev.mczachurski.mikroservices.extended-logging", version: "1.0.0")) | ||
|
||
let type = EnvelopeType() | ||
let content = self.getEnvelopeContent(level: level, | ||
message: message.description, | ||
eventId: eventId, | ||
currentDate: currentDate, | ||
fileName: file, | ||
function: function, | ||
line: line) | ||
|
||
let headerData = try jsonEncoder.encode(header) | ||
let typeData = try jsonEncoder.encode(type) | ||
let contentData = try jsonEncoder.encode(content) | ||
|
||
let data = headerData + newLine + typeData + newLine + contentData | ||
return data | ||
} | ||
|
||
private func prettify(_ metadata: Logger.Metadata) -> String? { | ||
return !metadata.isEmpty ? metadata.map { "\($0)=\($1)" }.joined(separator: " ") : nil | ||
} | ||
|
||
private func getEnvelopeContent(level: Logger.Level, message: String, eventId: String, currentDate: Date, fileName: String, function: String, line: UInt) -> EnvelopeContent { | ||
if level == Logger.Level.critical || level == Logger.Level.error { | ||
let lastFileName = String(fileName.split(separator: "/").last ?? "") | ||
|
||
return EnvelopeContent(level: level, | ||
eventId: eventId, | ||
platform: "swift", | ||
timestamp: currentDate.timeIntervalSince1970, | ||
environment: "production", | ||
message: nil, | ||
exception: EnvelopeErrorValues(values: [ | ||
EnvelopeErrorValue(type: "Error", | ||
value: message, | ||
stackTrace: EnvelopeFrames(frames: [ | ||
EnvelopeFrame(fileName: lastFileName, | ||
function: function, | ||
lineNumber: line) | ||
])) | ||
])) | ||
} else { | ||
return EnvelopeContent(level: level, | ||
eventId: eventId, | ||
platform: "swift", | ||
timestamp: currentDate.timeIntervalSince1970, | ||
environment: "production", | ||
message: message, | ||
exception: nil) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import Foundation | ||
import Logging | ||
|
||
public struct SentryLogger: LogHandler { | ||
|
||
public var metadata = Logger.Metadata() { | ||
didSet { | ||
self.logFormatter.metadata = self.metadata | ||
} | ||
} | ||
|
||
public var logLevel: Logger.Level = .info | ||
private let sentryWriter: SentryWriter | ||
private var logFormatter: LogFormatter | ||
|
||
public init(label: String, | ||
dsn: String, | ||
level: Logger.Level = .debug, | ||
metadata: Logger.Metadata = [:]) { | ||
self.label = label | ||
self.logLevel = level | ||
self.metadata = metadata | ||
self.logFormatter = SentryFormatter() | ||
self.sentryWriter = SentryWriter(dsn: dsn) | ||
} | ||
|
||
public let label: String | ||
|
||
public func log(level: Logger.Level, | ||
message: Logger.Message, | ||
metadata: Logger.Metadata?, | ||
file: String, | ||
function: String, | ||
line: UInt) { | ||
|
||
let message = try? self.logFormatter.format(label: self.label, | ||
level: level, | ||
message: message, | ||
metadata: metadata, | ||
file: file, | ||
function: function, | ||
line: line) | ||
|
||
self.sentryWriter.write(message: message) | ||
} | ||
|
||
public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? { | ||
get { | ||
return self.metadata[metadataKey] | ||
} | ||
set { | ||
self.metadata[metadataKey] = newValue | ||
} | ||
} | ||
|
||
// for testing | ||
public func wait() -> Void { | ||
// fileWriter.wait() | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
Sources/ExtendedLogging/SentryModels/EnvelopeContent.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import Foundation | ||
import Logging | ||
|
||
internal struct EnvelopeContent: Encodable { | ||
let level: Logger.Level | ||
let eventId: String | ||
let platform: String | ||
let timestamp: Double | ||
let environment: String | ||
let message: String? | ||
let exception: EnvelopeErrorValues? | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case level | ||
case eventId = "event_id" | ||
case platform | ||
case timestamp | ||
case environment | ||
case message | ||
case exception | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
Sources/ExtendedLogging/SentryModels/EnvelopeErrorValue.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
|
||
internal struct EnvelopeErrorValues: Encodable { | ||
let values: [EnvelopeErrorValue] | ||
} | ||
|
||
internal struct EnvelopeErrorValue: Encodable { | ||
let type: String | ||
let value: String | ||
let stackTrace: EnvelopeFrames | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case type | ||
case value | ||
case stackTrace = "stacktrace" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
|
||
internal struct EnvelopeFrames: Encodable { | ||
let frames: [EnvelopeFrame] | ||
} | ||
|
||
internal struct EnvelopeFrame: Encodable { | ||
let fileName: String | ||
let function: String | ||
let lineNumber: UInt | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case fileName = "filename" | ||
case function | ||
case lineNumber = "lineno" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import Foundation | ||
|
||
internal struct EnvelopeHeader: Encodable { | ||
let eventId: String | ||
let sentAt: Date | ||
let sdk: EnvelopeSdk | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case eventId = "event_id" | ||
case sentAt = "sent_at" | ||
case sdk | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import Foundation | ||
|
||
internal struct EnvelopeSdk: Encodable { | ||
let name: String | ||
let version: String | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Foundation | ||
|
||
internal struct EnvelopeType: Encodable { | ||
let type = "event" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import Foundation | ||
|
||
internal class SentryWriter { | ||
private let dsn: String; | ||
private let requestQueue = DispatchQueue.init(label: "SentryWriter", qos: .utility) | ||
|
||
init(dsn: String) { | ||
self.dsn = dsn | ||
} | ||
|
||
func write(message: Data?) { | ||
guard let data = message else { | ||
print("[SentryWriter] Logged message cannot be nil.") | ||
return | ||
} | ||
|
||
guard let url = self.convertToUrl() else { | ||
print("[SentryWriter] Cannot parse Sentry DSN url.") | ||
return | ||
} | ||
|
||
self.sendEvent(url: url, data: data) | ||
} | ||
|
||
private func sendEvent(url: URL, data: Data) { | ||
requestQueue.async { | ||
var urlRequest = URLRequest(url: url) | ||
urlRequest.httpMethod = "POST" | ||
urlRequest.httpBody = data | ||
|
||
let task = URLSession.shared.dataTask(with: urlRequest) | ||
task.resume() | ||
} | ||
} | ||
|
||
private func convertToUrl() -> URL? { | ||
let dsnUrl = URLComponents(string: self.dsn) | ||
|
||
guard let scheme = dsnUrl?.scheme else { | ||
return nil | ||
} | ||
|
||
guard let host = dsnUrl?.host else { | ||
return nil | ||
} | ||
|
||
guard let key = dsnUrl?.user else { | ||
return nil | ||
} | ||
|
||
guard let path = dsnUrl?.path else { | ||
return nil | ||
} | ||
|
||
let url = "\(scheme)://\(host)/api\(path)/envelope/?sentry_key=\(key)&sentry_version=7&sentry_client=dev.mczachurski.mikroservices.extended-logging%2F1.0.0" | ||
|
||
return URL(string: url) | ||
} | ||
} |