Skip to content

Commit

Permalink
Add unified service tagging (DD_ tags) (#6)
Browse files Browse the repository at this point in the history
* Add DD tags

* PR feedback
  • Loading branch information
gh123man authored May 10, 2023
1 parent 1ea08d5 commit c607dee
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 66 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func routes(_ app: Application) throws {

app.get { req -> String in

req.dogstatsd.increment("custom.swift.metric", tags: ["env": "prod"])
req.dogstatsd.increment("custom.swift.metric", tags: ["env:prod"])

return "It works!"
}
Expand Down
83 changes: 69 additions & 14 deletions Sources/Dogstatsd/Dogstatsd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,44 +60,45 @@ public protocol DogstatsdClient {
}

extension DogstatsdClient {
public func count(_ name: String, value: Int64, tags: [String: String] = [:], rate: Float = 1) {

public func count(_ name: String, value: Int64, tags: [String] = [], rate: Float = 1) {
sender.send(metric: .count(name: name, value: value), tags: tags, rate: rate)
}

public func increment(_ name: String, tags: [String: String] = [:], rate: Float = 1) {
public func increment(_ name: String, tags: [String] = [], rate: Float = 1) {
count(name, value: 1, tags: tags, rate: rate)
}

public func decrement(_ name: String, tags: [String: String] = [:], rate: Float = 1) {
public func decrement(_ name: String, tags: [String] = [], rate: Float = 1) {
count(name, value: -1, tags: tags, rate: rate)
}

public func gauge(_ name: String, value: Float64, tags: [String: String] = [:], rate: Float = 1) {
public func gauge(_ name: String, value: Float64, tags: [String] = [], rate: Float = 1) {
sender.send(metric: .gauge(name: name, value: value), tags: tags, rate: rate)
}

public func histogram(_ name: String, value: Float64, tags: [String: String] = [:], rate: Float = 1) {
public func histogram(_ name: String, value: Float64, tags: [String] = [], rate: Float = 1) {
sender.send(metric: .histogram(name: name, value: value), tags: tags, rate: rate)
}

public func distribution(_ name: String, value: Float64, tags: [String: String] = [:], rate: Float = 1) {
public func distribution(_ name: String, value: Float64, tags: [String] = [], rate: Float = 1) {
sender.send(metric: .distribution(name: name, value: value), tags: tags, rate: rate)
}

public func set(_ name: String, value: String, tags: [String: String] = [:], rate: Float = 1) {
public func set(_ name: String, value: String, tags: [String] = [], rate: Float = 1) {
sender.send(metric: .set(name: name, value: value), tags: tags, rate: rate)
}

public func timing(_ name: String, value: TimeInterval, tags: [String: String] = [:], rate: Float = 1) {
public func timing(_ name: String, value: TimeInterval, tags: [String] = [], rate: Float = 1) {
sender.send(metric: .timing(name: name, value: value), tags: tags, rate: rate)
}

public func serviceCheck(name: String,
status: ServiceCheckStatus,
timestamp: Date? = nil,
hostname: String? = nil,
message: String? = nil,
tags: [String: String] = [:]) {
status: ServiceCheckStatus,
timestamp: Date? = nil,
hostname: String? = nil,
message: String? = nil,
tags: [String] = []) {
sender.send(metric: .serviceCheck(name: name,
status: status,
timestamp: timestamp,
Expand All @@ -107,6 +108,56 @@ extension DogstatsdClient {
rate: 1)
}

// MARK: Convenience method overloads to support a dictionary of tags

public func count(_ name: String, value: Int64, tags: [String: String], rate: Float = 1) {
sender.send(metric: .count(name: name, value: value), tags: dictToList(tags: tags), rate: rate)
}

public func increment(_ name: String, tags: [String: String], rate: Float = 1) {
count(name, value: 1, tags: dictToList(tags: tags), rate: rate)
}

public func decrement(_ name: String, tags: [String: String], rate: Float = 1) {
count(name, value: -1, tags: dictToList(tags: tags), rate: rate)
}

public func gauge(_ name: String, value: Float64, tags: [String: String], rate: Float = 1) {
sender.send(metric: .gauge(name: name, value: value), tags: dictToList(tags: tags), rate: rate)
}

public func histogram(_ name: String, value: Float64, tags: [String: String], rate: Float = 1) {
sender.send(metric: .histogram(name: name, value: value), tags: dictToList(tags: tags), rate: rate)
}

public func distribution(_ name: String, value: Float64, tags: [String: String], rate: Float = 1) {
sender.send(metric: .distribution(name: name, value: value), tags: dictToList(tags: tags), rate: rate)
}

public func set(_ name: String, value: String, tags: [String: String], rate: Float = 1) {
sender.send(metric: .set(name: name, value: value), tags: dictToList(tags: tags), rate: rate)
}

public func timing(_ name: String, value: TimeInterval, tags: [String: String], rate: Float = 1) {
sender.send(metric: .timing(name: name, value: value), tags: dictToList(tags: tags), rate: rate)
}


public func serviceCheck(name: String,
status: ServiceCheckStatus,
timestamp: Date? = nil,
hostname: String? = nil,
message: String? = nil,
tags: [String: String]) {
sender.send(metric: .serviceCheck(name: name,
status: status,
timestamp: timestamp,
hostname: hostname,
message: message),
tags: dictToList(tags: tags),
rate: 1)
}

public func event(title: String,
text: String,
timestamp: Date? = nil,
Expand All @@ -115,7 +166,7 @@ extension DogstatsdClient {
priority: EventPriority? = nil,
sourceTypeName: String? = nil,
alertType: EventAlertType? = nil,
tags: [String: String] = [:]) {
tags: [String] = []) {

sender.send(metric: .event(title: title,
text: text,
Expand All @@ -130,6 +181,10 @@ extension DogstatsdClient {


}

private func dictToList(tags: [String: String]) -> [String] {
return tags.map { "\($0):\($1)" }
}
}

fileprivate extension Optional {
Expand Down
13 changes: 7 additions & 6 deletions Sources/Dogstatsd/Sender.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,25 @@ import Foundation

/// A dogstatsd sender interface.
public protocol StatsdSender {
var globalTags: [String] { get }

func sendRaw(metric: String)
}

extension StatsdSender {
public func send(metric: DogstatsdMetric, tags: [String: String], rate: Float) {
public func send(metric: DogstatsdMetric, tags: [String], rate: Float) {
guard shouldSample(rate: rate) else {
return
}

if tags.isEmpty {
let allTags = globalTags + tags

if allTags.isEmpty {
sendRaw(metric: metric.toWire)
return
}

let wireTags = tags.reduce("#") { reduced, kv in
"\(reduced)\(kv.key):\(kv.value),"
}.dropLast()

let wireTags = "#\(allTags.joined(separator: ","))"
sendRaw(metric: "\(metric.toWire)|\(wireTags)")
}

Expand Down
18 changes: 17 additions & 1 deletion Sources/Dogstatsd/Vapor+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ public class AsyncDogstatsdClient: DogstatsdClient {
return configuredSender
}

var globalTags: [String] {
[
"DD_ENV": "env",
"DD_SERVICE": "service",
"DD_VERSION": "version",
"DD_ENTITY_ID": "dd.internal.entity_id"
].compactMap { envVar, tag in
guard let val = Environment.get(envVar) else {
return nil
}
return "\(tag):\(val)"
}
}

private var configuredSender: StatsdSender?

public var config: ClientConfig? {
Expand All @@ -23,7 +37,8 @@ public class AsyncDogstatsdClient: DogstatsdClient {
do {
try configuredSender = VaporSender(client: SocketWriteClient(on: app.eventLoopGroup,
clientConfig: config),
eventLoop: app.eventLoopGroup.next())
eventLoop: app.eventLoopGroup.next(),
globalTags: globalTags)
} catch {
print("Warning: Failed to init dogstatsd client: \(error)")
}
Expand All @@ -33,6 +48,7 @@ public class AsyncDogstatsdClient: DogstatsdClient {
init(app: Application) {
self.app = app
}

}

extension Request {
Expand Down
5 changes: 4 additions & 1 deletion Sources/Dogstatsd/VaporSender.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import Vapor

/// A vapor specific non-blocking dogstatsd sender.
class VaporSender: StatsdSender {
var globalTags: [String]

private let client: SocketWriteClient

// Pin the event loop. this will be useful for aggregation in the future.
private let eventLoop: EventLoop

init(client: SocketWriteClient, eventLoop: EventLoop) {
init(client: SocketWriteClient, eventLoop: EventLoop, globalTags: [String]) {
self.client = client
self.eventLoop = eventLoop
self.globalTags = globalTags
}

func sendRaw(metric: String) {
Expand Down
Loading

0 comments on commit c607dee

Please sign in to comment.