Skip to content

Commit 2bfcccf

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 34396fb commit 2bfcccf

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
@@ -10,7 +10,7 @@ use crate::cheap_clone::CheapClone;
1010
use crate::data::graphql::{ObjectOrInterface, ObjectTypeExt, TypeExt};
1111
use crate::data::store::IdType;
1212
use crate::env::ENV_VARS;
13-
use crate::schema::{ast, META_FIELD_NAME, META_FIELD_TYPE, SCHEMA_TYPE_NAME};
13+
use crate::schema::{ast, LOGS_FIELD_NAME, META_FIELD_NAME, META_FIELD_TYPE, SCHEMA_TYPE_NAME};
1414

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

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

457461
api.definitions
458462
.extend(META_FIELD_SCHEMA.definitions.iter().cloned());
463+
api.definitions
464+
.extend(LOGS_FIELD_SCHEMA.definitions.iter().cloned());
459465
}
460466

461467
fn add_types_for_object_types(
@@ -1098,6 +1104,7 @@ fn add_query_type(api: &mut s::Document, input_schema: &InputSchema) -> Result<(
10981104
fields.append(&mut agg_fields);
10991105
fields.append(&mut fulltext_fields);
11001106
fields.push(meta_field());
1107+
fields.push(logs_field());
11011108

11021109
let typedef = s::TypeDefinition::Object(s::ObjectType {
11031110
position: q::Pos::default(),
@@ -1303,6 +1310,102 @@ fn meta_field() -> s::Field {
13031310
META_FIELD.clone()
13041311
}
13051312

1313+
fn logs_field() -> s::Field {
1314+
lazy_static! {
1315+
static ref LOGS_FIELD: s::Field = s::Field {
1316+
position: Pos::default(),
1317+
description: Some(
1318+
"Query execution logs emitted by the subgraph during indexing. \
1319+
Results are sorted by timestamp in descending order (newest first)."
1320+
.to_string()
1321+
),
1322+
name: LOGS_FIELD_NAME.to_string(),
1323+
arguments: vec![
1324+
// level: LogLevel
1325+
s::InputValue {
1326+
position: Pos::default(),
1327+
description: Some(
1328+
"Filter logs by severity level. Only logs at this level will be returned."
1329+
.to_string()
1330+
),
1331+
name: String::from("level"),
1332+
value_type: s::Type::NamedType(String::from("LogLevel")),
1333+
default_value: None,
1334+
directives: vec![],
1335+
},
1336+
// from: String (RFC3339 timestamp)
1337+
s::InputValue {
1338+
position: Pos::default(),
1339+
description: Some(
1340+
"Filter logs from this timestamp onwards (inclusive). \
1341+
Must be in RFC3339 format (e.g., '2024-01-15T10:30:00Z')."
1342+
.to_string()
1343+
),
1344+
name: String::from("from"),
1345+
value_type: s::Type::NamedType(String::from("String")),
1346+
default_value: None,
1347+
directives: vec![],
1348+
},
1349+
// to: String (RFC3339 timestamp)
1350+
s::InputValue {
1351+
position: Pos::default(),
1352+
description: Some(
1353+
"Filter logs until this timestamp (inclusive). \
1354+
Must be in RFC3339 format (e.g., '2024-01-15T23:59:59Z')."
1355+
.to_string()
1356+
),
1357+
name: String::from("to"),
1358+
value_type: s::Type::NamedType(String::from("String")),
1359+
default_value: None,
1360+
directives: vec![],
1361+
},
1362+
// search: String (full-text search)
1363+
s::InputValue {
1364+
position: Pos::default(),
1365+
description: Some(
1366+
"Search for logs containing this text in the message. \
1367+
Case-insensitive substring match. Maximum length: 1000 characters."
1368+
.to_string()
1369+
),
1370+
name: String::from("search"),
1371+
value_type: s::Type::NamedType(String::from("String")),
1372+
default_value: None,
1373+
directives: vec![],
1374+
},
1375+
// first: Int (default 100, max 1000)
1376+
s::InputValue {
1377+
position: Pos::default(),
1378+
description: Some(
1379+
"Maximum number of logs to return. Default: 100, Maximum: 1000."
1380+
.to_string()
1381+
),
1382+
name: String::from("first"),
1383+
value_type: s::Type::NamedType(String::from("Int")),
1384+
default_value: Some(s::Value::Int(100.into())),
1385+
directives: vec![],
1386+
},
1387+
// skip: Int (default 0, max 10000)
1388+
s::InputValue {
1389+
position: Pos::default(),
1390+
description: Some(
1391+
"Number of logs to skip (for pagination). Default: 0, Maximum: 10000."
1392+
.to_string()
1393+
),
1394+
name: String::from("skip"),
1395+
value_type: s::Type::NamedType(String::from("Int")),
1396+
default_value: Some(s::Value::Int(0.into())),
1397+
directives: vec![],
1398+
},
1399+
],
1400+
field_type: s::Type::NonNullType(Box::new(s::Type::ListType(Box::new(
1401+
s::Type::NonNullType(Box::new(s::Type::NamedType(String::from("_Log_")))),
1402+
)))),
1403+
directives: vec![],
1404+
};
1405+
}
1406+
LOGS_FIELD.clone()
1407+
}
1408+
13061409
#[cfg(test)]
13071410
mod tests {
13081411
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
@@ -41,6 +41,9 @@ pub const INTROSPECTION_SCHEMA_FIELD_NAME: &str = "__schema";
4141
pub const META_FIELD_TYPE: &str = "_Meta_";
4242
pub const META_FIELD_NAME: &str = "_meta";
4343

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

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

0 commit comments

Comments
 (0)