@@ -52,7 +52,8 @@ public async Task AddLogsAsync(IEnumerable<LogEntry> logs)
5252 new NpgsqlParameter ( $ "svc{ i } ", ( object ? ) log . ServiceName ?? DBNull . Value ) ,
5353 new NpgsqlParameter ( $ "lvl{ i } ", log . Level ) ,
5454 new NpgsqlParameter ( $ "msg{ i } ", log . Message ) ,
55- new NpgsqlParameter ( $ "meta{ i } ", NpgsqlDbType . Jsonb ) { Value = JsonSerializer . Serialize ( log . Metadata , _jsonOptions ) } ,
55+ new NpgsqlParameter ( $ "meta{ i } ", NpgsqlDbType . Jsonb )
56+ { Value = JsonSerializer . Serialize ( log . Metadata , _jsonOptions ) } ,
5657 new NpgsqlParameter ( $ "trace{ i } ", ( object ? ) log . TraceId ?? DBNull . Value ) ,
5758 new NpgsqlParameter ( $ "span{ i } ", ( object ? ) log . SpanId ?? DBNull . Value ) ,
5859 new NpgsqlParameter ( $ "host{ i } ", ( object ? ) log . Hostname ?? DBNull . Value ) ,
@@ -61,8 +62,9 @@ public async Task AddLogsAsync(IEnumerable<LogEntry> logs)
6162 i ++ ;
6263 }
6364
64- var sql = "INSERT INTO logs (timestamp, service_name, level, message, metadata, trace_id, span_id, hostname, environment) VALUES "
65- + string . Join ( ", " , sqlValues ) ;
65+ var sql =
66+ "INSERT INTO logs (timestamp, service_name, level, message, metadata, trace_id, span_id, hostname, environment) VALUES "
67+ + string . Join ( ", " , sqlValues ) ;
6668
6769 await using var conn = new NpgsqlConnection ( _connectionString ) ;
6870 await conn . OpenAsync ( ) ;
@@ -108,7 +110,7 @@ public async IAsyncEnumerable<IReadOnlyList<LogEntry>> GetBatchesAsync(
108110 var sqlParams = new List < NpgsqlParameter > ( ) ;
109111 BuildFilters ( sql , sqlParams , parameters ) ;
110112
111- sql . Append ( " ORDER BY timestamp ASC" ) ;
113+ sql . Append ( " ORDER BY timestamp ASC" ) ;
112114
113115 await using var conn = new NpgsqlConnection ( _connectionString ) ;
114116 await conn . OpenAsync ( ) ;
@@ -149,9 +151,84 @@ public async Task<long> CountLogsAsync(LogQueryParameters parameters)
149151 return ( long ) await cmd . ExecuteScalarAsync ( ) ;
150152 }
151153
154+ public async Task < LogMetadata > GetLogMetadataAsync ( )
155+ {
156+ const string sql = @"
157+ WITH
158+ lvl_counts AS (
159+ SELECT jsonb_object_agg(level, count) AS data
160+ FROM (SELECT level, COUNT(*) AS count FROM logs WHERE level IS NOT NULL GROUP BY level) t
161+ ),
162+ svc_counts AS (
163+ SELECT jsonb_object_agg(service_name, count) AS data
164+ FROM (SELECT service_name, COUNT(*) AS count FROM logs WHERE service_name IS NOT NULL GROUP BY service_name) t
165+ ),
166+ env_counts AS (
167+ SELECT jsonb_object_agg(environment, count) AS data
168+ FROM (SELECT environment, COUNT(*) AS count FROM logs WHERE environment IS NOT NULL GROUP BY environment) t
169+ ),
170+ host_counts AS (
171+ SELECT jsonb_object_agg(hostname, count) AS data
172+ FROM (SELECT hostname, COUNT(*) AS count FROM logs WHERE hostname IS NOT NULL GROUP BY hostname) t
173+ ),
174+ distincts AS (
175+ SELECT
176+ array_agg(DISTINCT level) AS levels,
177+ array_agg(DISTINCT environment) AS environments,
178+ array_agg(DISTINCT service_name) AS services,
179+ array_agg(DISTINCT hostname) AS hostnames,
180+ COUNT(*) AS log_count
181+ FROM logs
182+ )
183+ SELECT
184+ d.levels,
185+ d.environments,
186+ d.services,
187+ d.hostnames,
188+ d.log_count,
189+ l.data AS log_count_by_level,
190+ s.data AS log_count_by_service,
191+ e.data AS log_count_by_environment,
192+ h.data AS log_count_by_hostname
193+ FROM distincts d, lvl_counts l, svc_counts s, env_counts e, host_counts h;
194+
195+ " ;
196+
197+ await using var conn = new NpgsqlConnection ( _connectionString ) ;
198+ await conn . OpenAsync ( ) ;
199+ await using var cmd = new NpgsqlCommand ( sql , conn ) ;
200+ await using var reader = await cmd . ExecuteReaderAsync ( ) ;
201+
202+ if ( ! await reader . ReadAsync ( ) )
203+ throw new InvalidOperationException ( "Failed to read log metadata." ) ;
204+
205+ return new LogMetadata
206+ {
207+ LogLevels = reader . IsDBNull ( 0 ) ? [ ] : reader . GetFieldValue < string [ ] > ( 0 ) ,
208+ Environments = reader . IsDBNull ( 1 ) ? [ ] : reader . GetFieldValue < string [ ] > ( 1 ) ,
209+ Services = reader . IsDBNull ( 2 ) ? [ ] : reader . GetFieldValue < string [ ] > ( 2 ) ,
210+ Hostnames = reader . IsDBNull ( 3 ) ? [ ] : reader . GetFieldValue < string [ ] > ( 3 ) ,
211+ LogCount = reader . GetInt64 ( 4 ) ,
212+ LogCountByLevel = reader . IsDBNull ( 5 )
213+ ? new ( )
214+ : JsonSerializer . Deserialize < Dictionary < string , int > > ( reader . GetString ( 5 ) ) ! ,
215+ LogCountByService = reader . IsDBNull ( 6 )
216+ ? new ( )
217+ : JsonSerializer . Deserialize < Dictionary < string , int > > ( reader . GetString ( 6 ) ) ! ,
218+ LogCountByEnvironment = reader . IsDBNull ( 7 )
219+ ? new ( )
220+ : JsonSerializer . Deserialize < Dictionary < string , int > > ( reader . GetString ( 7 ) ) ! ,
221+ LogCountByHostname = reader . IsDBNull ( 8 )
222+ ? new ( )
223+ : JsonSerializer . Deserialize < Dictionary < string , int > > ( reader . GetString ( 8 ) ) !
224+ } ;
225+ }
226+
227+
152228 private async Task EnsurePartitionAsync ( DateTime timestamp )
153229 {
154- var startDate = timestamp . Date . AddDays ( - ( ( timestamp . Date - DateTime . MinValue . Date ) . Days % _partitionLengthInDays ) ) ;
230+ var startDate =
231+ timestamp . Date . AddDays ( - ( ( timestamp . Date - DateTime . MinValue . Date ) . Days % _partitionLengthInDays ) ) ;
155232 var endDate = startDate . AddDays ( _partitionLengthInDays ) ;
156233
157234 var partitionName = $ "logs_{ startDate : yyyy_MM_dd} _{ _partitionLengthInDays } d";
@@ -205,6 +282,7 @@ void AddFilter(string column, object? value)
205282 parameters . Add ( new NpgsqlParameter ( $ "p{ idx } ", query . From . Value ) ) ;
206283 idx ++ ;
207284 }
285+
208286 if ( query . To . HasValue )
209287 {
210288 sql . Append ( $ " AND timestamp <= @p{ idx } ") ;
@@ -224,6 +302,7 @@ void AddFilter(string column, object? value)
224302 sql . Append ( $ " AND message ILIKE @p{ idx } ") ;
225303 parameters . Add ( new NpgsqlParameter ( $ "p{ idx } ", $ "%{ query . Search } %") ) ;
226304 }
305+
227306 idx ++ ;
228307 }
229308
@@ -256,4 +335,4 @@ private LogEntry MapReader(NpgsqlDataReader reader) =>
256335 Hostname = reader . IsDBNull ( 7 ) ? null : reader . GetString ( 7 ) ,
257336 Environment = reader . IsDBNull ( 8 ) ? null : reader . GetString ( 8 ) ,
258337 } ;
259- }
338+ }
0 commit comments