Skip to content

Instantly share code, notes, and snippets.

@kateinoigakukun
Created December 26, 2020 05:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kateinoigakukun/73893159b78634c59ce505da8136b7a5 to your computer and use it in GitHub Desktop.
Save kateinoigakukun/73893159b78634c59ce505da8136b7a5 to your computer and use it in GitHub Desktop.
import Foundation
import Logging
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
public class DiscordLogHandler: LogHandler {
public let label: String
public var userName: String
/// See `LogHandler.metadata`.
public var metadata: Logger.Metadata
/// See `LogHandler.logLevel`.
public var logLevel: Logger.Level
public var webhookURL: URL
public var avatarURL: URL
private let session: URLSession
public init(
label: String, userName: String, avatarURL: URL,
webhookURL: URL, level: Logger.Level = .error, session: URLSession = .shared,
metadata: Logger.Metadata = [:]
) {
self.label = label
self.userName = userName
self.metadata = metadata
self.logLevel = level
self.webhookURL = webhookURL
self.avatarURL = avatarURL
self.session = session
}
public subscript(metadataKey key: String) -> Logger.Metadata.Value? {
get { metadata[key] }
set(newValue) { metadata[key] = newValue }
}
public func log(
level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, source: String,
file: String, function: String, line: UInt
) {
var text: String = ""
if self.logLevel <= .trace {
text += "[ \(self.label) ] "
}
text += "[ \(level.name) ]" + " " + message.description
let allMetadata = (metadata ?? [:]).merging(self.metadata) { (a, _) in a }
if !allMetadata.isEmpty {
// only log metadata if not empty
text += " " + allMetadata.sortedDescriptionWithoutQuotes
}
if self.logLevel <= .debug {
text += " (" + file + ":" + line.description + ")"
}
let webhook = DiscordWebhook(
username: userName, avatarUrl: avatarURL.absoluteString, content: "`\(text)`")
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let body: Data
do {
body = try encoder.encode(webhook)
} catch {
print(
"[ ERROR ] DiscordLogHandler.log failed to encode body '\(error)' (\(#file):\(#line)"
)
return
}
var request = URLRequest(url: webhookURL)
request.httpMethod = "POST"
request.httpBody = body
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request) { data, response, error in
if let data = data, let response = response as? HTTPURLResponse {
guard 200..<300 ~= response.statusCode else {
let errorResponse = String(data: data, encoding: .utf8)
print(
"[ ERROR ] DiscordLogHandler.log failed to send request '\(errorResponse ?? "no message")' (\(#file):\(#line)"
)
return
}
} else if let error = error {
print(
"[ ERROR ] DiscordLogHandler.log failed to send request '\(error)' (\(#file):\(#line)"
)
} else {
print(
"[ ERROR ] DiscordLogHandler.log got unexpected request result (\(#file):\(#line)"
)
}
}
task.resume()
}
}
struct DiscordWebhook: Codable {
let username: String
let avatarUrl: String
let content: String
}
extension Logger.Metadata {
fileprivate var sortedDescriptionWithoutQuotes: String {
let contents = Array(self)
.sorted(by: { $0.0 < $1.0 })
.map { "\($0.description): \($1)" }
.joined(separator: ", ")
return "[\(contents)]"
}
}
extension Logger.Level {
fileprivate var name: String {
switch self {
case .trace: return "TRACE"
case .debug: return "DEBUG"
case .info: return "INFO"
case .notice: return "NOTICE"
case .warning: return "WARNING"
case .error: return "ERROR"
case .critical: return "CRITICAL"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment