-
Notifications
You must be signed in to change notification settings - Fork 97
/
ServiceFabricDiagnosticPipelineFactory.cs
137 lines (122 loc) · 7.04 KB
/
ServiceFabricDiagnosticPipelineFactory.cs
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
127
128
129
130
131
132
133
134
135
136
137
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Fabric;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Diagnostics.EventFlow;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.ServiceFabric;
using Validation;
namespace Microsoft.Diagnostics.EventFlow.ServiceFabric
{
public static class ServiceFabricDiagnosticPipelineFactory
{
// The "<name>" capture group will match any combination of, one or more, words '\w'
// and separators as: dot '.', underscore '_' (captured by \w), dash '\-', colon ':', slash '/', and hash '#'.
public static readonly string FabricConfigurationValueReference = @"servicefabric:/(?<section>\w+)/(?<name>[\w.\-:/#]+)";
public static readonly string FabricConfigurationFileReference = @"servicefabricfile:/(?<filename>.+)";
public const string DefaultConfigurationFileName = "eventFlowConfig.json";
public static DiagnosticPipeline CreatePipeline(
string healthEntityName,
string configurationFileName = DefaultConfigurationFileName,
string configurationPackageName = ServiceFabricConfigurationProvider.DefaultConfigurationPackageName)
{
// TODO: dynamically re-configure the pipeline when configuration changes, without stopping the service
Requires.NotNullOrWhiteSpace(healthEntityName, nameof(healthEntityName));
Requires.NotNullOrWhiteSpace(configurationFileName, nameof(configurationFileName));
Requires.NotNullOrWhiteSpace(configurationPackageName, nameof(configurationPackageName));
var healthReporter = new ServiceFabricHealthReporter(healthEntityName);
CodePackageActivationContext activationContext = FabricRuntime.GetActivationContext();
ConfigurationPackage configPackage;
try
{
configPackage = activationContext.GetConfigurationPackageObject(configurationPackageName);
}
catch
{
string errorMessage = $"{nameof(ServiceFabricDiagnosticPipelineFactory)}: configuration package '{configurationPackageName}' is missing or inaccessible";
healthReporter.ReportProblem(errorMessage, EventFlowContextIdentifiers.Configuration);
throw;
}
string configFilePath = Path.Combine(configPackage.Path, configurationFileName);
if (!File.Exists(configFilePath))
{
string errorMessage = $"{nameof(ServiceFabricDiagnosticPipelineFactory)}: configuration file '{configFilePath}' is missing or inaccessible";
healthReporter.ReportProblem(errorMessage, EventFlowContextIdentifiers.Configuration);
throw new Exception(errorMessage);
}
ConfigurationBuilder configBuilder = new ConfigurationBuilder();
configBuilder.AddJsonFile(configFilePath);
configBuilder.AddServiceFabric(ServiceFabricConfigurationProvider.DefaultConfigurationPackageName);
IConfigurationRoot configurationRoot = configBuilder.Build().ApplyFabricConfigurationOverrides(configPackage.Path, healthReporter);
return DiagnosticPipelineFactory.CreatePipeline(configurationRoot, new ServiceFabricHealthReporter(healthEntityName));
}
internal static IConfigurationRoot ApplyFabricConfigurationOverrides(
this IConfigurationRoot configurationRoot,
string configPackagePath,
IHealthReporter healthReporter)
{
Debug.Assert(configurationRoot != null);
Debug.Assert(!string.IsNullOrWhiteSpace(configPackagePath));
Debug.Assert(healthReporter != null);
Regex fabricValueReferenceRegex = new Regex(FabricConfigurationValueReference, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(100));
Regex fabricFileReferenceRegex = new Regex(FabricConfigurationFileReference, RegexOptions.CultureInvariant, TimeSpan.FromMilliseconds(100));
// Use ToList() to ensure that configuration is fully enumerated before starting to modify it.
foreach (var kvp in configurationRoot.AsEnumerable().ToList())
{
if (kvp.Value == null)
{
continue;
}
try
{
Match valueReferenceMatch = fabricValueReferenceRegex.Match(kvp.Value);
if (valueReferenceMatch.Success)
{
string valueReferencePath = ConfigurationPath.Combine(valueReferenceMatch.Groups["section"].Value, valueReferenceMatch.Groups["name"].Value);
string newValue = configurationRoot[valueReferencePath];
if (newValue == null)
{
healthReporter.ReportWarning(
$"Configuration value reference '{kvp.Value}' was encountered but no corresponding configuration value was found using path '{valueReferencePath}'",
EventFlowContextIdentifiers.Configuration);
}
else
{
configurationRoot[kvp.Key] = newValue;
}
}
Match fileReferenceMatch = fabricFileReferenceRegex.Match(kvp.Value);
if (fileReferenceMatch.Success)
{
string configFileName = fileReferenceMatch.Groups["filename"].Value;
if (string.IsNullOrWhiteSpace(configFileName))
{
healthReporter.ReportWarning(
$"Configuration file reference '{kvp.Value}' was encountered but the file name part is missing",
EventFlowContextIdentifiers.Configuration);
}
else
{
string configFilePath = Path.Combine(configPackagePath, configFileName);
configurationRoot[kvp.Key] = configFilePath;
}
}
}
catch (RegexMatchTimeoutException)
{
healthReporter.ReportWarning(
$"Configuration entry with key '{kvp.Key}' and value '{kvp.Value}' could not be checked if it represents a configuration value reference--a timeout occurred when the value was being parsed.",
EventFlowContextIdentifiers.Configuration);
continue;
}
}
return configurationRoot;
}
}
}