/
SlackLogger.swift
126 lines (115 loc) · 5.31 KB
/
SlackLogger.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//
// SlackLogger.swift
// VaporLogging
//
// Created by Jari Koopman on 22/06/2018.
//
import Foundation
import Vapor
public struct SlackLoggerConfig: Service {
/// URL to post to in PROD mode
var prodURL: String
/// URL to post to in DEV mode, defaults to `prodURL`
var devURL: String
/// Filters to use when getting a log message
var filters: [LoggerFilter]
/// Log messages using pretty rich embeds
var useEmbeds: Bool
/// If not using embeds, message format to use
var messageFormat: String
/// Creates a new SlackLoggerConfig
///
/// - parameters:
/// - prodURL: URL to post to in Production env
/// - devURL: URL to post to in Dev env, defaults to `prodURL`
/// - filters: Filters to use when getting a log message
/// - useEmbeds: Log messages using pretty rich embeds
/// - messageFormat:
/// Messages can contain the following variables:
///
/// {{message}} - REQUIRED - The logged message
///
/// {{level}} - The log level, ie INFO, DEBUG
///
/// {{file}} - The file from where the log was triggered
///
/// {{function}} - The function that triggered the log
///
/// {{line}} - The line on which the log was triggered
///
/// {{column}} - The column the log was triggered on
public init(prodURL: String, devURL: String = "", filters: [LoggerFilter] = [], useEmbeds: Bool, messageFormat: String = "") throws {
guard prodURL.contains("hooks.slack.com/services"), devURL == "" ? true : devURL.contains("hooks.slack.com/services") else {
throw VaporError(identifier: "VaporLogging.invalidSlackURL", reason: "Invalid Slack webhook URL provided", suggestedFixes: ["Provide a valid Slack webhook URL"])
}
guard messageFormat != "" || useEmbeds else {
throw VaporError(identifier: "VaporLogging.noMessageFormat", reason: "No message format provided", suggestedFixes: ["Add a message format to your `SlackLoggerConfig`"])
}
self.prodURL = prodURL
self.devURL = devURL == "" ? prodURL : devURL
self.filters = filters
self.useEmbeds = useEmbeds
self.messageFormat = messageFormat
}
}
public class SlackLogger: ServiceType, Logger {
let config: SlackLoggerConfig
let client: Client
let isRelease: Bool
public required init(_ config: SlackLoggerConfig, _ client: Client, _ isRelease: Bool) {
self.config = config
self.client = client
self.isRelease = isRelease
}
public static var serviceSupports: [Any.Type] {
return [Logger.self]
}
public static func makeService(for worker: Container) throws -> Self {
return try .init(worker.make(), worker.make(), worker.environment.isRelease)
}
public struct SlackPayload: Content {
var attachments: [SlackAttachment]?
var text: String?
}
public struct SlackAttachment: Content {
struct Field: Content {
var title: String
var value: String
}
var title: String
var ts: Double
var text: String
var color: String
var fields: [Field]
}
public func createPayload(_ message: String, at level: LogLevel, file: String, function: String, line: UInt, column: UInt) throws -> SlackPayload {
if self.config.useEmbeds {
let payload = SlackPayload(attachments: [SlackAttachment(title: "[ \(level.description) ]", ts: Date().timeIntervalSince1970, text: message, color: level.hexColor, fields: [SlackAttachment.Field(title: "File", value: "\(file.split(separator: "/").last!):\(line)"), SlackAttachment.Field(title: "Function", value: function)])], text: nil)
return payload
} else {
guard self.config.messageFormat != "" else {
throw VaporError(identifier: "VaporLogging.noMessageFormat", reason: "No message format provided", suggestedFixes: ["Add a message format to your `SlackLoggerConfig`"])
}
var content = self.config.messageFormat
content = content.replacingOccurrences(of: "{{message}}", with: message)
content = content.replacingOccurrences(of: "{{level}}", with: level.description)
content = content.replacingOccurrences(of: "{{file}}", with: file)
content = content.replacingOccurrences(of: "{{function}}", with: function)
content = content.replacingOccurrences(of: "{{line}}", with: "\(line)")
content = content.replacingOccurrences(of: "{{column}}", with: "\(column)")
return SlackPayload(attachments: nil, text: content)
}
}
public func log(_ string: String, at level: LogLevel, file: String, function: String, line: UInt, column: UInt) {
var shouldFilter: Bool = false
for filter in config.filters {
if !filter(string, level, file, function, line, column) {
shouldFilter = true
}
}
guard !shouldFilter else { return }
_ = self.client.post(self.isRelease ? config.prodURL : config.devURL, headers: HTTPHeaders(), beforeSend: { (req) in
try req.content.encode(self.createPayload(string, at: level, file: file, function: function, line: line, column: column))
})
}
}