1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
+ // Licensed under the MIT License. See License.txt in the project root for license information.
3
+
4
+ using System ;
5
+ using System . IO ;
6
+ using System . IO . Abstractions ;
7
+ using System . Linq ;
8
+ using System . Threading . Tasks ;
9
+ using Microsoft . Azure . WebJobs . Script . Diagnostics . Extensions ;
10
+ using Microsoft . Extensions . Logging ;
11
+ using Newtonsoft . Json ;
12
+
13
+ namespace Microsoft . Azure . WebJobs . Script . WebHost . Metrics
14
+ {
15
+ internal sealed class LegionMetricsFileManager
16
+ {
17
+ private readonly IFileSystem _fileSystem ;
18
+ private readonly int _maxFileCount ;
19
+ private readonly ILogger _logger ;
20
+ private readonly JsonSerializerSettings _serializerSettings ;
21
+
22
+ public LegionMetricsFileManager ( string metricsFilePath , IFileSystem fileSystem , ILogger logger , int maxFileCount )
23
+ {
24
+ _fileSystem = fileSystem ?? throw new ArgumentNullException ( nameof ( fileSystem ) ) ;
25
+ _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
26
+ MetricsFilePath = metricsFilePath ;
27
+ _maxFileCount = maxFileCount ;
28
+
29
+ _serializerSettings = new JsonSerializerSettings
30
+ {
31
+ NullValueHandling = NullValueHandling . Ignore
32
+ } ;
33
+ }
34
+
35
+ // Internal access for testing only
36
+ internal string MetricsFilePath { get ; set ; }
37
+
38
+ private bool PrepareDirectoryForFile ( )
39
+ {
40
+ if ( string . IsNullOrEmpty ( MetricsFilePath ) )
41
+ {
42
+ return false ;
43
+ }
44
+
45
+ // ensure the directory exists
46
+ var metricsDirectoryInfo = _fileSystem . Directory . CreateDirectory ( MetricsFilePath ) ;
47
+
48
+ // ensure we're under the max file count
49
+ var files = metricsDirectoryInfo . GetFiles ( ) . OrderBy ( p => p . CreationTime ) . ToList ( ) ;
50
+ if ( files . Count < _maxFileCount )
51
+ {
52
+ return true ;
53
+ }
54
+
55
+ // we're at or over limit
56
+ // delete enough files that we have space to write a new one
57
+ int numToDelete = files . Count - _maxFileCount + 1 ;
58
+ var filesToDelete = files . Take ( numToDelete ) . ToArray ( ) ;
59
+
60
+ _logger . LogDebug ( $ "Deleting { filesToDelete . Length } metrics file(s).") ;
61
+
62
+ Parallel . ForEach ( filesToDelete , file =>
63
+ {
64
+ try
65
+ {
66
+ file . Delete ( ) ;
67
+ }
68
+ catch ( Exception ex ) when ( ! ex . IsFatal ( ) )
69
+ {
70
+ // best effort
71
+ _logger . LogError ( ex , $ "Error deleting metrics file '{ file . FullName } '.") ;
72
+ }
73
+ } ) ;
74
+
75
+ files = metricsDirectoryInfo . GetFiles ( ) . OrderBy ( p => p . CreationTime ) . ToList ( ) ;
76
+
77
+ // return true if we have space for a new file
78
+ return files . Count < _maxFileCount ;
79
+ }
80
+
81
+ public async Task PublishMetricsAsync ( object metrics )
82
+ {
83
+ string fileName = string . Empty ;
84
+
85
+ try
86
+ {
87
+ bool metricsPublishEnabled = ! string . IsNullOrEmpty ( MetricsFilePath ) ;
88
+ if ( metricsPublishEnabled && ! PrepareDirectoryForFile ( ) )
89
+ {
90
+ return ;
91
+ }
92
+
93
+ string metricsContent = JsonConvert . SerializeObject ( metrics , _serializerSettings ) ;
94
+ _logger . PublishingMetrics ( metricsContent ) ;
95
+
96
+ if ( metricsPublishEnabled )
97
+ {
98
+ fileName = $ "{ Guid . NewGuid ( ) . ToString ( ) . ToLowerInvariant ( ) } .json";
99
+ string filePath = Path . Combine ( MetricsFilePath , fileName ) ;
100
+
101
+ using var streamWriter = _fileSystem . File . CreateText ( filePath ) ;
102
+ await streamWriter . WriteAsync ( metricsContent ) ;
103
+ }
104
+ }
105
+ catch ( Exception ex ) when ( ! ex . IsFatal ( ) )
106
+ {
107
+ // TODO: consider using a retry strategy here
108
+ _logger . LogError ( ex , $ "Error writing metrics file '{ fileName } '.") ;
109
+ }
110
+ }
111
+ }
112
+ }
0 commit comments