Skip to content

Commit 62f98d9

Browse files
authored
Merge pull request #17 from instana/feature/update_max_length_for_values
Do not drop values longer than max allowed length.
2 parents 5d6584c + 608ea5d commit 62f98d9

File tree

23 files changed

+521
-397
lines changed

23 files changed

+521
-397
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/InstanaAgentIntegrationTests.xcscheme

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
<Test
3333
Identifier = "BasicIntegrationServerTest">
3434
</Test>
35+
<Test
36+
Identifier = "BeaconTests">
37+
</Test>
3538
<Test
3639
Identifier = "CoreBeaconExtensionsTests">
3740
</Test>
@@ -44,6 +47,9 @@
4447
<Test
4548
Identifier = "CustomBeaconTests">
4649
</Test>
50+
<Test
51+
Identifier = "DefaultIgnoredURLsTests">
52+
</Test>
4753
<Test
4854
Identifier = "FramerateDropMonitorTests">
4955
</Test>

Sources/InstanaAgent/Beacons/Beacons Types/Beacon.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import Foundation
22

33
/// Base class for Beacon.
44
class Beacon {
5+
56
let id = UUID()
67
var timestamp: Instana.Types.Milliseconds = Date().millisecondsSince1970
78
let viewName: String?
89

910
init(timestamp: Instana.Types.Milliseconds = Date().millisecondsSince1970,
1011
viewName: String? = nil) {
1112
self.timestamp = timestamp
12-
self.viewName = viewName
13+
self.viewName = InstanaProperties.validate(view: viewName)
1314
}
1415
}
1516

Sources/InstanaAgent/Beacons/Beacons Types/CustomBeacon.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ class CustomBeacon: Beacon {
1717
let duration: Instana.Types.Milliseconds?
1818
let backendTracingID: String?
1919
let error: Error?
20-
let meta: [String: String]?
20+
let metaData: MetaData?
2121

2222
init(timestamp: Instana.Types.Milliseconds? = nil,
2323
name: String,
2424
duration: Instana.Types.Milliseconds? = nil,
2525
backendTracingID: String? = nil,
2626
error: Error? = nil,
27-
meta: [String: String]? = nil,
27+
metaData: MetaData? = nil,
2828
viewName: String? = CustomBeaconDefaultViewNameID) {
2929
self.duration = duration
3030
self.name = name
3131
self.error = error
32-
self.meta = meta
32+
self.metaData = metaData
3333
self.backendTracingID = backendTracingID
3434
var start = Date().millisecondsSince1970
3535
if let duration = duration {

Sources/InstanaAgent/Beacons/Beacons Types/HTTPBeacon.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import Foundation
22

33
class HTTPBeacon: Beacon {
4+
static let maxLengthURL: Int = 4096
5+
46
let duration: Instana.Types.Milliseconds
57
let method: String
68
let url: URL
@@ -22,7 +24,8 @@ class HTTPBeacon: Beacon {
2224
let path = !url.path.isEmpty ? url.path : nil
2325
self.duration = duration
2426
self.method = method
25-
self.url = url
27+
let urlString = url.absoluteString.cleanEscapeAndTruncate(at: Self.maxLengthURL, trailing: "")
28+
self.url = URL(string: urlString) ?? url
2629
self.path = path
2730
self.responseCode = responseCode
2831
self.responseSize = responseSize

Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon+Extensions.swift

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,25 @@ extension CoreBeacon {
1919

2020
func formattedKVPair(key: String, value: Any) -> String? {
2121
guard Mirror.isNotNil(value: value) else { return nil }
22-
if let dict = value as? [AnyHashable: AnyObject] {
22+
if let dict = value as? MetaData {
2323
return dict.asString(prefix: key)
2424
}
25-
let value = "\(value)".cleanAndEscape()
25+
let value = "\(value)".coreBeaconClean()
26+
guard !value.isEmpty else { return nil }
2627
return "\(key)\t\(value)"
2728
}
2829
}
2930

30-
extension Dictionary {
31-
func asString(prefix: String) -> String {
32-
return map {
33-
let value = "\($0.value)".cleanAndEscape()
34-
return "\(prefix)_\($0.key)\t\(value)"
31+
extension MetaData {
32+
func asString(prefix: String) -> String? {
33+
guard count > 0 else { return nil }
34+
return compactMap {
35+
guard !$0.value.isEmpty else { return nil }
36+
return "\(prefix)_\($0.key)\t\($0.value.coreBeaconClean())"
3537
}.joined(separator: "\n")
3638
}
3739
}
3840

39-
extension String {
40-
func cleanAndEscape() -> Self {
41-
var trimmed = trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
42-
trimmed = trimmed.truncated(at: Int(CoreBeacon.maxBytesPerField))
43-
trimmed = trimmed.replacingOccurrences(of: "\\", with: "\\\\")
44-
trimmed = trimmed.replacingOccurrences(of: "\n", with: "\\n")
45-
trimmed = trimmed.replacingOccurrences(of: "\t", with: "\\t")
46-
return trimmed
47-
}
48-
}
49-
5041
extension Collection where Element == CoreBeacon {
5142
var asString: String { compactMap { $0.asString }.joined(separator: "\n\n") }
5243
}

Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeacon.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ enum BeaconType: String, Equatable, Codable, CustomStringConvertible {
1818
/// That means we also loose the type information, so we need treat all fields as String
1919
struct CoreBeacon: Codable {
2020
/**
21-
* The max byte for each field (bytes)
21+
* The max length for each field
2222
*
2323
*
2424
* Default: To be discussed if it can be dynamic
2525
*/
26-
static let maxBytesPerField: Instana.Types.Bytes = 10000
26+
static let maxLengthPerField: Int = 16384
2727

2828
/**
2929
* The type of the beacon.
@@ -113,7 +113,7 @@ struct CoreBeacon: Codable {
113113
*
114114
* optional
115115
*/
116-
var m: [String: String]?
116+
var m: MetaData?
117117

118118
/**
119119
* User ID

Sources/InstanaAgent/Beacons/CoreBeacon/CoreBeaconFactory.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ extension CoreBeacon {
4343
ui = properties.user?.id
4444
un = properties.user?.name
4545
ue = properties.user?.email
46-
m = properties.metaData
46+
m = !properties.metaData.isEmpty ? properties.metaData : nil
4747
}
4848

4949
mutating func append(_ beacon: HTTPBeacon) {
@@ -91,7 +91,7 @@ extension CoreBeacon {
9191
let useCurrentVisibleViewName = beacon.viewName == CustomBeaconDefaultViewNameID
9292
v = useCurrentVisibleViewName ? properties.viewNameForCurrentAppState : beacon.viewName
9393
cen = beacon.name
94-
m = beacon.meta
94+
m = beacon.metaData
9595
if let error = beacon.error {
9696
add(error: error)
9797
}
Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,63 @@
11
import Foundation
22

3+
typealias MetaData = [String: String]
4+
35
struct InstanaProperties: Equatable {
46
struct User: Equatable {
7+
static let userValueMaxLength = 128
8+
59
/// Unique identifier for the user
6-
var id: String
10+
var id: String {
11+
didSet { id = Self.validate(value: id) ?? id }
12+
}
13+
714
/// User's email address
8-
var email: String?
15+
var email: String? {
16+
didSet { email = Self.validate(value: email) }
17+
}
18+
919
/// User's full name
10-
var name: String?
20+
var name: String? {
21+
didSet { name = Self.validate(value: name) }
22+
}
23+
24+
init(id: String, email: String?, name: String?) {
25+
self.id = Self.validate(value: id) ?? id
26+
self.email = Self.validate(value: email)
27+
self.name = Self.validate(value: name)
28+
}
29+
30+
static func validate(value: String?) -> String? {
31+
guard let value = value else { return nil }
32+
return value.cleanEscapeAndTruncate(at: userValueMaxLength)
33+
}
1134
}
1235

1336
var user: User?
14-
var metaData: [String: String]?
15-
var view: String?
37+
private(set) var metaData = MetaData()
38+
39+
static let viewMaxLength = 256
40+
var view: String? {
41+
didSet { view = Self.validate(view: view) }
42+
}
43+
44+
init(user: User? = nil, view: String? = nil) {
45+
self.user = user
46+
self.view = Self.validate(view: view)
47+
}
48+
49+
mutating func appendMetaData(_ key: String, _ value: String) {
50+
let key = MetaData.validate(key: key)
51+
let value = MetaData.validate(value: value)
52+
if metaData.count < MetaData.Max.numberOfMetaEntries {
53+
metaData[key] = value
54+
}
55+
}
56+
57+
static func validate(view: String?) -> String? {
58+
guard let value = view else { return nil }
59+
return value.cleanEscapeAndTruncate(at: viewMaxLength)
60+
}
1661
}
1762

1863
extension InstanaProperties {
@@ -31,11 +76,6 @@ extension InstanaProperties {
3176
}
3277

3378
class InstanaPropertyHandler: NSObject {
34-
struct Const {
35-
static let maximumNumberOfMetaDataFields = 50
36-
static let maximumLengthPerMetaDataField = 256
37-
}
38-
3979
private var unsafe_properties = InstanaProperties()
4080
private let lock = NSLock()
4181
var properties: InstanaProperties {
@@ -52,21 +92,26 @@ class InstanaPropertyHandler: NSObject {
5292
lock.unlock()
5393
}
5494
}
95+
}
96+
97+
extension MetaData {
98+
struct Max {
99+
static let numberOfMetaEntries = 64
100+
static let lengthMetaValue = 256
101+
static let lengthMetaKey = 98
102+
}
55103

56-
func validate(value: String) -> Bool {
57-
if value.count > Const.maximumLengthPerMetaDataField {
58-
Instana.current?.session.logger.add("Instana: MetaData value reached maximum length (\(Const.maximumLengthPerMetaDataField)).", level: .warning)
59-
return false
104+
static func validate(value: String) -> String {
105+
if value.count > Max.lengthMetaValue {
106+
Instana.current?.session.logger.add("Instana: MetaData value reached maximum length (\(Max.lengthMetaValue)) Truncating value.", level: .warning)
60107
}
61-
return value.count <= Const.maximumLengthPerMetaDataField
108+
return value.cleanEscapeAndTruncate(at: Max.lengthMetaValue)
62109
}
63110

64-
func validate(keys: [String]) -> Bool {
65-
if keys.count > Const.maximumNumberOfMetaDataFields {
66-
// swiftlint:disable:next line_length
67-
Instana.current?.session.logger.add("Instana: MetaData reached maximum number (\(Const.maximumNumberOfMetaDataFields)) of valid fields.", level: .warning)
68-
return false
111+
static func validate(key: String) -> String {
112+
if key.count > Max.lengthMetaKey {
113+
Instana.current?.session.logger.add("Instana: MetaData key reached maximum length (\(Max.lengthMetaKey)) Truncating key.", level: .warning)
69114
}
70-
return true
115+
return key.cleanEscapeAndTruncate(at: Max.lengthMetaKey)
71116
}
72117
}

Sources/InstanaAgent/Instana.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,7 @@ import UIKit
157157
@objc
158158
public static func setMeta(value: String, key: String) {
159159
guard let propertyHandler = Instana.current?.session.propertyHandler else { return }
160-
guard propertyHandler.validate(value: value) else { return }
161-
var metaData = propertyHandler.properties.metaData ?? [:]
162-
metaData[key] = value
163-
if propertyHandler.validate(keys: Array(metaData.keys)) {
164-
propertyHandler.properties.metaData = metaData
165-
}
160+
propertyHandler.properties.appendMetaData(key, value)
166161
}
167162

168163
/// User-specific information
@@ -240,7 +235,7 @@ import UIKit
240235
duration: duration,
241236
backendTracingID: backendTracingID,
242237
error: error,
243-
meta: meta,
238+
metaData: meta,
244239
viewName: viewName)
245240
Instana.current?.monitors.reporter.submit(beacon)
246241
}
Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
import Foundation
22

33
extension String {
4-
func truncated(at length: Int, trailing: String = "") -> String {
4+
func coreBeaconClean() -> String {
5+
cleanEscapeAndTruncate(at: CoreBeacon.maxLengthPerField)
6+
}
7+
8+
func cleanEscapeAndTruncate(at length: Int, trailing: String = "") -> String {
9+
return cleanEscape().maxLength(length, trailing: trailing)
10+
}
11+
12+
func cleanEscape() -> String {
13+
var trimmed = trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
14+
trimmed = trimmed.replacingOccurrences(of: "\\", with: "\\\\")
15+
trimmed = trimmed.replacingOccurrences(of: "\n", with: "\\n")
16+
trimmed = trimmed.replacingOccurrences(of: "\t", with: "\\t")
17+
return trimmed
18+
}
19+
20+
func maxLength(_ length: Int, trailing: String = "") -> String {
521
if count <= length {
622
return self
723
}
8-
let truncated = prefix(length)
9-
return truncated + trailing
24+
return String(prefix(length - trailing.count)) + trailing
1025
}
1126
}

0 commit comments

Comments
 (0)