Skip to content

Commit 4df624f

Browse files
Add Serialization & Deserialization of Sink File Properties (#2752)
## Why make this change? - This change solves issue #2576. We need to add the new `File Sink` properties that will later be used to send logs to `.txt` files locally. The properties need to have all the necessary components to be serialized and deserialized from the config file. ## What is this change? - This change adds the `File Sink` properties to the schema file. - Creates a new file `FileSinkConverter.cs` where the properties are serialized and deserialized. - Creates a new file `FileSinkOptions.cs` where the deserialized properties are turned to usable objects and adds the object to the `Telemetry` options. #### JSON Configuration Schema The configuration now supports the following structure under `runtime.telemetry`: ```json { "runtime": { "telemetry": { "file": { "enabled": true, "path": "/logs/dab-log.txt", "rolling-interval": "Day", "retained-file-count-limit": 7, "file-size-limit-bytes": 1048576 } } } } ``` ## How was this tested? - [ ] Integration Tests - [x] Unit Tests - [x] Manual Tests Tested that the newly created properties inside the config file are saved correctly as objects inside DAB and if the properties are written incorrectly, an exception is raised. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]>
1 parent b032ff8 commit 4df624f

File tree

8 files changed

+514
-8
lines changed

8 files changed

+514
-8
lines changed

schemas/dab.draft.schema.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,40 @@
483483
"required": [ "auth" ]
484484
}
485485
},
486+
"file": {
487+
"type": "object",
488+
"additionalProperties": false,
489+
"properties": {
490+
"enabled": {
491+
"type": "boolean",
492+
"description": "Enable/disable file sink telemetry logging.",
493+
"default": false
494+
},
495+
"path": {
496+
"type": "string",
497+
"description": "File path for telemetry logs.",
498+
"default": "/logs/dab-log.txt"
499+
},
500+
"rolling-interval": {
501+
"type": "string",
502+
"description": "Rolling interval for log files.",
503+
"default": "Day",
504+
"enum": ["Minute", "Hour", "Day", "Month", "Year", "Infinite"]
505+
},
506+
"retained-file-count-limit": {
507+
"type": "integer",
508+
"description": "Maximum number of retained log files.",
509+
"default": 1,
510+
"minimum": 1
511+
},
512+
"file-size-limit-bytes": {
513+
"type": "integer",
514+
"description": "Maximum file size in bytes before rolling.",
515+
"default": 1048576,
516+
"minimum": 1
517+
}
518+
}
519+
},
486520
"log-level": {
487521
"type": "object",
488522
"description": "Global configuration of log level, defines logging severity levels for specific classes, when 'null' it will set logging level based on 'host: mode' property",
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
using Azure.DataApiBuilder.Config.ObjectModel;
7+
8+
namespace Azure.DataApiBuilder.Config.Converters;
9+
class FileSinkConverter : JsonConverter<FileSinkOptions>
10+
{
11+
// Determines whether to replace environment variable with its
12+
// value or not while deserializing.
13+
private bool _replaceEnvVar;
14+
15+
/// <param name="replaceEnvVar">
16+
/// Whether to replace environment variable with its value or not while deserializing.
17+
/// </param>
18+
public FileSinkConverter(bool replaceEnvVar)
19+
{
20+
_replaceEnvVar = replaceEnvVar;
21+
}
22+
23+
/// <summary>
24+
/// Defines how DAB reads File Sink options and defines which values are
25+
/// used to instantiate FileSinkOptions.
26+
/// </summary>
27+
/// <exception cref="JsonException">Thrown when improperly formatted File Sink options are provided.</exception>
28+
public override FileSinkOptions? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
29+
{
30+
if (reader.TokenType == JsonTokenType.StartObject)
31+
{
32+
bool? enabled = null;
33+
string? path = null;
34+
RollingIntervalMode? rollingInterval = null;
35+
int? retainedFileCountLimit = null;
36+
int? fileSizeLimitBytes = null;
37+
38+
while (reader.Read())
39+
{
40+
if (reader.TokenType == JsonTokenType.EndObject)
41+
{
42+
return new FileSinkOptions(enabled, path, rollingInterval, retainedFileCountLimit, fileSizeLimitBytes);
43+
}
44+
45+
string? propertyName = reader.GetString();
46+
47+
reader.Read();
48+
switch (propertyName)
49+
{
50+
case "enabled":
51+
if (reader.TokenType is not JsonTokenType.Null)
52+
{
53+
enabled = reader.GetBoolean();
54+
}
55+
56+
break;
57+
58+
case "path":
59+
if (reader.TokenType is not JsonTokenType.Null)
60+
{
61+
path = reader.DeserializeString(_replaceEnvVar);
62+
}
63+
64+
break;
65+
66+
case "rolling-interval":
67+
if (reader.TokenType is not JsonTokenType.Null)
68+
{
69+
rollingInterval = EnumExtensions.Deserialize<RollingIntervalMode>(reader.DeserializeString(_replaceEnvVar)!);
70+
}
71+
72+
break;
73+
74+
case "retained-file-count-limit":
75+
if (reader.TokenType is not JsonTokenType.Null)
76+
{
77+
try
78+
{
79+
retainedFileCountLimit = reader.GetInt32();
80+
}
81+
catch (FormatException)
82+
{
83+
throw new JsonException($"The JSON token value is of the incorrect numeric format.");
84+
}
85+
86+
if (retainedFileCountLimit <= 0)
87+
{
88+
throw new JsonException($"Invalid retained-file-count-limit: {retainedFileCountLimit}. Specify a number > 0.");
89+
}
90+
}
91+
92+
break;
93+
94+
case "file-size-limit-bytes":
95+
if (reader.TokenType is not JsonTokenType.Null)
96+
{
97+
try
98+
{
99+
fileSizeLimitBytes = reader.GetInt32();
100+
}
101+
catch (FormatException)
102+
{
103+
throw new JsonException($"The JSON token value is of the incorrect numeric format.");
104+
}
105+
106+
if (retainedFileCountLimit <= 0)
107+
{
108+
throw new JsonException($"Invalid file-size-limit-bytes: {fileSizeLimitBytes}. Specify a number > 0.");
109+
}
110+
}
111+
112+
break;
113+
114+
default:
115+
throw new JsonException($"Unexpected property {propertyName}");
116+
}
117+
}
118+
}
119+
120+
throw new JsonException("Failed to read the File Sink Options");
121+
}
122+
123+
/// <summary>
124+
/// When writing the FileSinkOptions back to a JSON file, only write the properties
125+
/// if they are user provided. This avoids polluting the written JSON file with properties
126+
/// the user most likely omitted when writing the original DAB runtime config file.
127+
/// This Write operation is only used when a RuntimeConfig object is serialized to JSON.
128+
/// </summary>
129+
public override void Write(Utf8JsonWriter writer, FileSinkOptions value, JsonSerializerOptions options)
130+
{
131+
writer.WriteStartObject();
132+
133+
if (value?.UserProvidedEnabled is true)
134+
{
135+
writer.WritePropertyName("enabled");
136+
JsonSerializer.Serialize(writer, value.Enabled, options);
137+
}
138+
139+
if (value?.UserProvidedPath is true)
140+
{
141+
writer.WritePropertyName("path");
142+
JsonSerializer.Serialize(writer, value.Path, options);
143+
}
144+
145+
if (value?.UserProvidedRollingInterval is true)
146+
{
147+
writer.WritePropertyName("rolling-interval");
148+
JsonSerializer.Serialize(writer, value.RollingInterval, options);
149+
}
150+
151+
if (value?.UserProvidedRetainedFileCountLimit is true)
152+
{
153+
writer.WritePropertyName("retained-file-count-limit");
154+
JsonSerializer.Serialize(writer, value.RetainedFileCountLimit, options);
155+
}
156+
157+
if (value?.UserProvidedFileSizeLimitBytes is true)
158+
{
159+
writer.WritePropertyName("file-size-limit-bytes");
160+
JsonSerializer.Serialize(writer, value.FileSizeLimitBytes, options);
161+
}
162+
163+
writer.WriteEndObject();
164+
}
165+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Text.Json.Serialization;
6+
7+
namespace Azure.DataApiBuilder.Config.ObjectModel;
8+
9+
/// <summary>
10+
/// Represents the options for configuring file sink telemetry.
11+
/// </summary>
12+
public record FileSinkOptions
13+
{
14+
/// <summary>
15+
/// Default enabled for File Sink.
16+
/// </summary>
17+
public const bool DEFAULT_ENABLED = false;
18+
19+
/// <summary>
20+
/// Default path for File Sink.
21+
/// </summary>
22+
public const string DEFAULT_PATH = "/logs/dab-log.txt";
23+
24+
/// <summary>
25+
/// Default rolling interval for File Sink.
26+
/// </summary>
27+
public const string DEFAULT_ROLLING_INTERVAL = nameof(RollingIntervalMode.Day);
28+
29+
/// <summary>
30+
/// Default retained file count limit for File Sink.
31+
/// </summary>
32+
public const int DEFAULT_RETAINED_FILE_COUNT_LIMIT = 1;
33+
34+
/// <summary>
35+
/// Default file size limit bytes for File Sink.
36+
/// </summary>
37+
public const int DEFAULT_FILE_SIZE_LIMIT_BYTES = 1048576;
38+
39+
/// <summary>
40+
/// Whether File Sink is enabled.
41+
/// </summary>
42+
public bool Enabled { get; init; }
43+
44+
/// <summary>
45+
/// Path to the file where logs will be uploaded.
46+
/// </summary>
47+
public string? Path { get; init; }
48+
49+
/// <summary>
50+
/// Time it takes for files with logs to be discarded.
51+
/// </summary>
52+
public string? RollingInterval { get; init; }
53+
54+
/// <summary>
55+
/// Amount of files that can exist simultaneously in which logs are saved.
56+
/// </summary>
57+
public int? RetainedFileCountLimit { get; init; }
58+
59+
/// <summary>
60+
/// File size limit in bytes before a new file needs to be created.
61+
/// </summary>
62+
public int? FileSizeLimitBytes { get; init; }
63+
64+
[JsonConstructor]
65+
public FileSinkOptions(bool? enabled = null, string? path = null, RollingIntervalMode? rollingInterval = null, int? retainedFileCountLimit = null, int? fileSizeLimitBytes = null)
66+
{
67+
if (enabled is not null)
68+
{
69+
Enabled = (bool)enabled;
70+
UserProvidedEnabled = true;
71+
}
72+
else
73+
{
74+
Enabled = DEFAULT_ENABLED;
75+
}
76+
77+
if (path is not null)
78+
{
79+
Path = path;
80+
UserProvidedPath = true;
81+
}
82+
else
83+
{
84+
Path = DEFAULT_PATH;
85+
}
86+
87+
if (rollingInterval is not null)
88+
{
89+
RollingInterval = rollingInterval.ToString();
90+
UserProvidedRollingInterval = true;
91+
}
92+
else
93+
{
94+
RollingInterval = DEFAULT_ROLLING_INTERVAL;
95+
}
96+
97+
if (retainedFileCountLimit is not null)
98+
{
99+
RetainedFileCountLimit = retainedFileCountLimit;
100+
UserProvidedRetainedFileCountLimit = true;
101+
}
102+
else
103+
{
104+
RetainedFileCountLimit = DEFAULT_RETAINED_FILE_COUNT_LIMIT;
105+
}
106+
107+
if (fileSizeLimitBytes is not null)
108+
{
109+
FileSizeLimitBytes = fileSizeLimitBytes;
110+
UserProvidedFileSizeLimitBytes = true;
111+
}
112+
else
113+
{
114+
FileSizeLimitBytes = DEFAULT_FILE_SIZE_LIMIT_BYTES;
115+
}
116+
}
117+
118+
/// <summary>
119+
/// Flag which informs CLI and JSON serializer whether to write enabled
120+
/// property/value to the runtime config file.
121+
/// When user doesn't provide the enabled property/value, which signals DAB to use the default,
122+
/// the DAB CLI should not write the default value to a serialized config.
123+
/// </summary>
124+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
125+
[MemberNotNullWhen(true, nameof(Enabled))]
126+
public bool UserProvidedEnabled { get; init; } = false;
127+
128+
/// <summary>
129+
/// Flag which informs CLI and JSON serializer whether to write path
130+
/// property/value to the runtime config file.
131+
/// When user doesn't provide the path property/value, which signals DAB to use the default,
132+
/// the DAB CLI should not write the default value to a serialized config.
133+
/// </summary>
134+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
135+
[MemberNotNullWhen(true, nameof(Path))]
136+
public bool UserProvidedPath { get; init; } = false;
137+
138+
/// <summary>
139+
/// Flag which informs CLI and JSON serializer whether to write rolling-interval
140+
/// property/value to the runtime config file.
141+
/// When user doesn't provide the rolling-interval property/value, which signals DAB to use the default,
142+
/// the DAB CLI should not write the default value to a serialized config.
143+
/// </summary>
144+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
145+
[MemberNotNullWhen(true, nameof(RollingInterval))]
146+
public bool UserProvidedRollingInterval { get; init; } = false;
147+
148+
/// <summary>
149+
/// Flag which informs CLI and JSON serializer whether to write retained-file-count-limit
150+
/// property/value to the runtime config file.
151+
/// When user doesn't provide the retained-file-count-limit property/value, which signals DAB to use the default,
152+
/// the DAB CLI should not write the default value to a serialized config.
153+
/// </summary>
154+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
155+
[MemberNotNullWhen(true, nameof(RetainedFileCountLimit))]
156+
public bool UserProvidedRetainedFileCountLimit { get; init; } = false;
157+
158+
/// <summary>
159+
/// Flag which informs CLI and JSON serializer whether to write file-size-limit-bytes
160+
/// property/value to the runtime config file.
161+
/// When user doesn't provide the file-size-limit-bytes property/value, which signals DAB to use the default,
162+
/// the DAB CLI should not write the default value to a serialized config.
163+
/// </summary>
164+
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
165+
[MemberNotNullWhen(true, nameof(FileSizeLimitBytes))]
166+
public bool UserProvidedFileSizeLimitBytes { get; init; } = false;
167+
}

0 commit comments

Comments
 (0)