Skip to content

Commit 1c2af42

Browse files
committed
graphql,graph: Add GraphQL schema and resolver for _logs query
Adds GraphQL API for querying subgraph logs: Schema types: - LogLevel enum (CRITICAL, ERROR, WARNING, INFO, DEBUG) - _Log_ type with id, timestamp, level, text, arguments, meta - _LogArgument_ type for structured key-value pairs - _LogMeta_ type for source location (module, line, column) Query field (_logs) with filters: - level: Filter by log level - from/to: Timestamp range (ISO 8601) - search: Text search in log messages - first/skip: Pagination (max 1000, skip max 10000)
1 parent f0b223d commit 1c2af42

5 files changed

Lines changed: 347 additions & 5 deletions

File tree

graph/src/schema/api.rs

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::cheap_clone::CheapClone;
1111
use crate::data::graphql::{ObjectOrInterface, ObjectTypeExt, TypeExt};
1212
use crate::data::store::IdType;
1313
use crate::env::ENV_VARS;
14-
use crate::schema::{ast, META_FIELD_NAME, META_FIELD_TYPE, SCHEMA_TYPE_NAME};
14+
use crate::schema::{ast, LOGS_FIELD_NAME, META_FIELD_NAME, META_FIELD_TYPE, SCHEMA_TYPE_NAME};
1515

1616
use crate::data::graphql::ext::{
1717
camel_cased_names, DefinitionExt, DirectiveExt, DocumentExt, ValueExt,
@@ -350,7 +350,7 @@ pub(in crate::schema) fn api_schema(
350350
) -> Result<s::Document, APISchemaError> {
351351
// Refactor: Don't clone the schema.
352352
let mut api = init_api_schema(input_schema)?;
353-
add_meta_field_type(&mut api.document);
353+
add_builtin_field_types(&mut api.document);
354354
add_types_for_object_types(&mut api, input_schema)?;
355355
add_types_for_interface_types(&mut api, input_schema)?;
356356
add_types_for_aggregation_types(&mut api, input_schema)?;
@@ -445,18 +445,24 @@ fn init_api_schema(input_schema: &InputSchema) -> Result<Schema, APISchemaError>
445445
.map_err(|e| APISchemaError::SchemaCreationFailed(e.to_string()))
446446
}
447447

448-
/// Adds a global `_Meta_` type to the schema. The `_meta` field
449-
/// accepts values of this type
450-
fn add_meta_field_type(api: &mut s::Document) {
448+
/// Adds built-in field types to the schema. Currently adds `_Meta_` and `_Log_` types
449+
/// which are used by the `_meta` and `_logs` fields respectively.
450+
fn add_builtin_field_types(api: &mut s::Document) {
451451
lazy_static! {
452452
static ref META_FIELD_SCHEMA: s::Document = {
453453
let schema = include_str!("meta.graphql");
454454
s::parse_schema(schema).expect("the schema `meta.graphql` is invalid")
455455
};
456+
static ref LOGS_FIELD_SCHEMA: s::Document = {
457+
let schema = include_str!("logs.graphql");
458+
s::parse_schema(schema).expect("the schema `logs.graphql` is invalid")
459+
};
456460
}
457461

458462
api.definitions
459463
.extend(META_FIELD_SCHEMA.definitions.iter().cloned());
464+
api.definitions
465+
.extend(LOGS_FIELD_SCHEMA.definitions.iter().cloned());
460466
}
461467

462468
fn add_types_for_object_types(
@@ -1073,6 +1079,7 @@ fn add_query_type(api: &mut s::Document, input_schema: &InputSchema) -> Result<(
10731079
fields.append(&mut agg_fields);
10741080
fields.append(&mut fulltext_fields);
10751081
fields.push(meta_field());
1082+
fields.push(logs_field());
10761083

10771084
let typedef = s::TypeDefinition::Object(s::ObjectType {
10781085
position: Pos::default(),
@@ -1278,6 +1285,102 @@ fn meta_field() -> s::Field {
12781285
META_FIELD.clone()
12791286
}
12801287

1288+
fn logs_field() -> s::Field {
1289+
lazy_static! {
1290+
static ref LOGS_FIELD: s::Field = s::Field {
1291+
position: Pos::default(),
1292+
description: Some(
1293+
"Query execution logs emitted by the subgraph during indexing. \
1294+
Results are sorted by timestamp in descending order (newest first)."
1295+
.to_string()
1296+
),
1297+
name: LOGS_FIELD_NAME.to_string(),
1298+
arguments: vec![
1299+
// level: LogLevel
1300+
s::InputValue {
1301+
position: Pos::default(),
1302+
description: Some(
1303+
"Filter logs by severity level. Only logs at this level will be returned."
1304+
.to_string()
1305+
),
1306+
name: String::from("level"),
1307+
value_type: s::Type::NamedType(String::from("LogLevel")),
1308+
default_value: None,
1309+
directives: vec![],
1310+
},
1311+
// from: String (RFC3339 timestamp)
1312+
s::InputValue {
1313+
position: Pos::default(),
1314+
description: Some(
1315+
"Filter logs from this timestamp onwards (inclusive). \
1316+
Must be in RFC3339 format (e.g., '2024-01-15T10:30:00Z')."
1317+
.to_string()
1318+
),
1319+
name: String::from("from"),
1320+
value_type: s::Type::NamedType(String::from("String")),
1321+
default_value: None,
1322+
directives: vec![],
1323+
},
1324+
// to: String (RFC3339 timestamp)
1325+
s::InputValue {
1326+
position: Pos::default(),
1327+
description: Some(
1328+
"Filter logs until this timestamp (inclusive). \
1329+
Must be in RFC3339 format (e.g., '2024-01-15T23:59:59Z')."
1330+
.to_string()
1331+
),
1332+
name: String::from("to"),
1333+
value_type: s::Type::NamedType(String::from("String")),
1334+
default_value: None,
1335+
directives: vec![],
1336+
},
1337+
// search: String (full-text search)
1338+
s::InputValue {
1339+
position: Pos::default(),
1340+
description: Some(
1341+
"Search for logs containing this text in the message. \
1342+
Case-insensitive substring match. Maximum length: 1000 characters."
1343+
.to_string()
1344+
),
1345+
name: String::from("search"),
1346+
value_type: s::Type::NamedType(String::from("String")),
1347+
default_value: None,
1348+
directives: vec![],
1349+
},
1350+
// first: Int (default 100, max 1000)
1351+
s::InputValue {
1352+
position: Pos::default(),
1353+
description: Some(
1354+
"Maximum number of logs to return. Default: 100, Maximum: 1000."
1355+
.to_string()
1356+
),
1357+
name: String::from("first"),
1358+
value_type: s::Type::NamedType(String::from("Int")),
1359+
default_value: Some(s::Value::Int(100.into())),
1360+
directives: vec![],
1361+
},
1362+
// skip: Int (default 0, max 10000)
1363+
s::InputValue {
1364+
position: Pos::default(),
1365+
description: Some(
1366+
"Number of logs to skip (for pagination). Default: 0, Maximum: 10000."
1367+
.to_string()
1368+
),
1369+
name: String::from("skip"),
1370+
value_type: s::Type::NamedType(String::from("Int")),
1371+
default_value: Some(s::Value::Int(0.into())),
1372+
directives: vec![],
1373+
},
1374+
],
1375+
field_type: s::Type::NonNullType(Box::new(s::Type::ListType(Box::new(
1376+
s::Type::NonNullType(Box::new(s::Type::NamedType(String::from("_Log_")))),
1377+
)))),
1378+
directives: vec![],
1379+
};
1380+
}
1381+
LOGS_FIELD.clone()
1382+
}
1383+
12811384
#[cfg(test)]
12821385
mod tests {
12831386
use crate::{

graph/src/schema/logs.graphql

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
The severity level of a log entry.
3+
Log levels are ordered from most to least severe: CRITICAL > ERROR > WARNING > INFO > DEBUG
4+
"""
5+
enum LogLevel {
6+
"Critical errors that require immediate attention"
7+
CRITICAL
8+
"Error conditions that indicate a failure"
9+
ERROR
10+
"Warning conditions that may require attention"
11+
WARNING
12+
"Informational messages about normal operations"
13+
INFO
14+
"Detailed diagnostic information for debugging"
15+
DEBUG
16+
}
17+
18+
"""
19+
A log entry emitted by a subgraph during indexing.
20+
Logs can be generated by the subgraph's AssemblyScript code using the `log.*` functions.
21+
"""
22+
type _Log_ {
23+
"Unique identifier for this log entry"
24+
id: String!
25+
"The deployment hash of the subgraph that emitted this log"
26+
subgraphId: String!
27+
"The timestamp when the log was emitted, in RFC3339 format (e.g., '2024-01-15T10:30:00Z')"
28+
timestamp: String!
29+
"The severity level of the log entry"
30+
level: LogLevel!
31+
"The log message text"
32+
text: String!
33+
"Additional structured data passed to the log function as key-value pairs"
34+
arguments: [_LogArgument_!]!
35+
"Metadata about the source location in the subgraph code where the log was emitted"
36+
meta: _LogMeta_!
37+
}
38+
39+
"""
40+
A key-value pair of additional data associated with a log entry.
41+
These correspond to arguments passed to the log function in the subgraph code.
42+
"""
43+
type _LogArgument_ {
44+
"The parameter name"
45+
key: String!
46+
"The parameter value, serialized as a string"
47+
value: String!
48+
}
49+
50+
"""
51+
Source code location metadata for a log entry.
52+
Indicates where in the subgraph's AssemblyScript code the log statement was executed.
53+
"""
54+
type _LogMeta_ {
55+
"The module or file path where the log was emitted"
56+
module: String!
57+
"The line number in the source file"
58+
line: Int!
59+
"The column number in the source file"
60+
column: Int!
61+
}

graph/src/schema/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ pub const INTROSPECTION_SCHEMA_FIELD_NAME: &str = "__schema";
4242
pub const META_FIELD_TYPE: &str = "_Meta_";
4343
pub const META_FIELD_NAME: &str = "_meta";
4444

45+
pub const LOGS_FIELD_TYPE: &str = "_Log_";
46+
pub const LOGS_FIELD_NAME: &str = "_logs";
47+
4548
pub const INTROSPECTION_TYPE_FIELD_NAME: &str = "__type";
4649

4750
pub const BLOCK_FIELD_TYPE: &str = "_Block_";

0 commit comments

Comments
 (0)