Hasura GraphQL engine logs
Accessing logs
Based on your deployment method, the Hasura GraphQL engine logs can be accessed as follows:
If you’re looking for more in-depth logging information, along with a console for browsing logs, please see Observability with Hasura Cloud.
Different log-types
The Hasura GraphQL engine has different kind of log-types depending on the sub-system or the layer. A log-type is simply
the type
field in a log line, which indicates which sub-system the log comes from.
For example, the HTTP webserver logs incoming requests as an access log and is called http-log
. Similarly logs from
the websocket layer are called websocket-log
, logs from the event trigger system are called event-trigger
etc.
You can configure the GraphQL engine to enable/disable certain log-types using the the --enabled-log-types
flag or the
HASURA_GRAPHQL_ENABLED_LOG_TYPES
env var. See
GraphQL Engine server config reference
Default enabled log-types are: startup, http-log, webhook-log, websocket-log, jwk-refresh-log
All the log-types that can be enabled/disabled are:
Log type | Description | Log Level |
---|---|---|
startup | Information that is logged during startup | info |
query-log | Logs: the entire GraphQL query with variables, generated SQL statements (only for database queries, not for mutations/subscriptions or remote schema and action queries), the operation name (if provided in the GraphQL request) | info |
http-log | Http access and error logs at the webserver layer (handling GraphQL and metadata requests) | info and error |
websocket-log | Websocket events and error logs at the websocket server layer (handling GraphQL requests) | info and error |
webhook-log | Logs responses and errors from the authorization webhook (if setup) | info and error |
jwk-refresh-log | Logs information and errors about periodic refreshing of JWK | info and error |
Configurable log-types
Apart from the above, there are other internal log-types which cannot be configured:
Log type | Description | Log Level |
---|---|---|
pg-client | Logs from the postgres client library | warn |
metadata | Logs inconsistent metadata items | warn |
telemetry-log | Logs error (if any) while sending out telemetry data | info |
event-trigger | Logs HTTP responses from the webhook, HTTP exceptions and internal errors | info and error |
ws-server | Debug logs from the websocket server, mostly used internally for debugging | debug |
schema-sync-thread | Logs internal events, when it detects schema has changed on Postgres and when it reloads the schema | info and error |
health-check-log | Logs source health check events which includes health status of a data source | info and warn |
Internal log-types
Logging levels
You can set the desired logging level on the server using the log-level
flag or the HASURA_GRAPHQL_LOG_LEVEL
env
var. See GraphQL Engine server config reference.
The default log-level is info
.
Setting a log-level will print all logs of priority greater than the set level. The log-level hierarchy is:
debug > info > warn > error
For example, setting --log-level=warn
, will enable all warn and error level logs only. So even if you have enabled
query-log
it won't be printed as the level of query-log
is info
.
See log-types for more details on log-level of each log-type.
Log structure and metrics
All requests are identified by a request id. If the client sends a x-request-id
header then that is used, otherwise a
request id is generated for each request. This is also sent back to the client as a response header (x-request-id
).
This is useful to correlate logs from the server and the client.
query-log structure
On enabling verbose logging, i.e. enabling query-log
, GraphQL engine will log the full GraphQL query object on each
request.
It will also log the generated SQL for GraphQL queries (but not mutations and subscriptions).
{
"timestamp": "2019-06-03T13:25:10.915+0530",
"level": "info",
"type": "query-log",
"detail": {
"kind": "database",
"request_id": "840f952d-c489-4d21-a87a-cc23ad17926a",
"query": {
"variables": {
"limit": 10
},
"operationName": "getProfile",
"query": "query getProfile($limit: Int!) {\n profile(limit: $limit, where: {username: {_like: \"%a%\"}}) {\n username\n }\n myusername: profile (where: {username: {_eq: \"foobar\"}}) {\n username\n }\n}\n"
},
"generated_sql": {
"profile": {
"prepared_arguments": ["{\"x-hasura-role\":\"admin\"}", "%a%"],
"query": "SELECT coalesce(json_agg(\"root\" ), '[]' ) AS \"root\" FROM (SELECT row_to_json((SELECT \"_1_e\" FROM (SELECT \"_0_root.base\".\"username\" AS \"username\" ) AS \"_1_e\" ) ) AS \"root\" FROM (SELECT * FROM \"public\".\"profile\" WHERE ((\"public\".\"profile\".\"username\") LIKE ($2)) ) AS \"_0_root.base\" LIMIT 10 ) AS \"_2_root\" "
},
"myusername": {
"prepared_arguments": ["{\"x-hasura-role\":\"admin\"}", "foobar"],
"query": "SELECT coalesce(json_agg(\"root\" ), '[]' ) AS \"root\" FROM (SELECT row_to_json((SELECT \"_1_e\" FROM (SELECT \"_0_root.base\".\"username\" AS \"username\" ) AS \"_1_e\" ) ) AS \"root\" FROM (SELECT * FROM \"public\".\"profile\" WHERE ((\"public\".\"profile\".\"username\") = ($2)) ) AS \"_0_root.base\" ) AS \"_2_root\" "
}
}
}
}
The type
of in the log with be query-log
. All the details are nested under the detail
key.
This log contains 3 important fields:
kind
: indicates the type or kind of operation.kind
can bedatabase
,action
,remote-schema
,cached
orintrospection
request_id
: A unique ID for each request. If the client sends ax-request-id
header then that is respected, otherwise a UUID is generated for each request. This is useful to correlate betweenhttp-log
andquery-log
.query
: Contains the full GraphQL request including the variables and operation name.generated_sql
: this contains the generated SQL for GraphQL queries. For mutations and subscriptions this field will benull
.
http-log structure
This is how the HTTP access logs look like:
- On success response:
{
"timestamp": "2019-05-30T23:40:24.654+0530",
"level": "info",
"type": "http-log",
"detail": {
"request_id": "072b3617-6653-4fd5-b5ee-580e9d098c3d",
"operation": {
"query_execution_time": 0.009240042,
"user_vars": {
"x-hasura-role": "user"
},
"error": null,
"request_id": "072b3617-6653-4fd5-b5ee-580e9d098c3d",
"parameterized_query_hash": "7116865cef017c3b09e5c9271b0e182a6dcf4c01",
"response_size": 105,
"query": null
},
"http_info": {
"status": 200,
"http_version": "HTTP/1.1",
"url": "/v1/graphql",
"ip": "127.0.0.1",
"method": "POST"
}
}
}
- On error response:
{
"timestamp": "2019-05-29T15:22:37.834+0530",
"level": "error",
"type": "http-log",
"detail": {
"operation": {
"query_execution_time": 0.000656144,
"user_vars": {
"x-hasura-role": "user",
"x-hasura-user-id": "1"
},
"error": {
"path": "$.selectionSet.profile.selectionSet.usernamex",
"error": "field 'usernamex' not found in type: 'profile'",
"code": "validation-failed"
},
"request_id": "072b3617-6653-4fd5-b5ee-580e9d098c3d",
"response_size": 142,
"query": {
"variables": {
"limit": 10
},
"operationName": "getProfile",
"query": "query getProfile($limit: Int!) { profile(limit: $limit, where:{username: {_like: \"%a%\"}}) { usernamex} }"
}
},
"http_info": {
"status": 200,
"http_version": "HTTP/1.1",
"url": "/v1/graphql",
"ip": "127.0.0.1",
"method": "POST"
}
}
The type
in the log will be http-log
for HTTP access/error log. This log contains basic information about the HTTP
request and the GraphQL operation.
It has two important "keys" under the detail
section - operation
and http_info
.
http_info
lists various information regarding the HTTP request, e.g. IP address, URL path, HTTP status code etc.
operation
lists various information regarding the GraphQL query/operation.
query_execution_time
: the time taken to parse the GraphQL query (from JSON request), compile it to SQL with permissions and user session variables, and then executing it and fetching the results back from Postgres. The unit is in seconds.user_vars
: contains the user session variables. Or thex-hasura-*
session variables inferred from the authorization mode.request_id
: A unique ID for each request. If the client sends ax-request-id
header then that is respected, otherwise a UUID is generated for each request.response_size
: Size of the response in bytes.error
: optional. Will contain the error object when there is an error, otherwise this will benull
. This key can be used to detect if there is an error in the request. The status code for error requests will be200
on thev1/graphql
endpoint.query
: optional. This will contain the GraphQL query object only when there is an error. On successful response this will benull
.parametrized_query_hash
(*): Hash of the incoming GraphQL query after resolving variables with all the leaf nodes (i.e. scalar values) discarded. This value will only be logged when the request is successful. For example, all the queries mentioned in the below snippet will compute the same parametrized query hash.
# sample query
query {
authors(where: { id: { _eq: 2 } }) {
id
name
}
}
# query with a different leaf value to that of the sample query
query {
authors(where: { id: { _eq: 203943 } }) {
id
name
}
}
# query with use of a variable, the value of
# the variable `id` can be anything
query {
authors(where: { id: { _eq: $id } }) {
id
name
}
}
# query with use of a boolean expression variable,
# the value when the `whereBoolExp` is in the form of
#
# {
# "id": {
# "_eq": <id>
# }
# }
query {
authors(where: $whereBoolExp) {
id
name
}
}
(*) - Supported only in Cloud and Enterprise editions only
websocket-log structure
This is how the Websocket logs look like:
- On successful operation start:
{
"timestamp": "2019-06-10T10:52:54.247+0530",
"level": "info",
"type": "websocket-log",
"detail": {
"event": {
"type": "operation",
"detail": {
"request_id": "d2ede87d-5cb7-44b6-8736-1d898117722a",
"operation_id": "1",
"query": {
"variables": {},
"query": "subscription {\n author {\n name\n }\n}\n"
},
"operation_type": {
"type": "started"
},
"operation_name": null
}
},
"connection_info": {
"websocket_id": "f590dd18-75db-4602-8693-8150239df7f7",
"jwt_expiry": null,
"msg": null
},
"user_vars": {
"x-hasura-role": "admin"
}
}
}
- On operation stop:
{
"timestamp": "2019-06-10T11:01:40.939+0530",
"level": "info",
"type": "websocket-log",
"detail": {
"event": {
"type": "operation",
"detail": {
"request_id": null,
"operation_id": "1",
"query": null,
"operation_type": {
"type": "stopped"
},
"operation_name": null
}
},
"connection_info": {
"websocket_id": "7f782190-fd58-4305-a83f-8e17177b204e",
"jwt_expiry": null,
"msg": null
},
"user_vars": {
"x-hasura-role": "admin"
}
}
}
- On error:
{
"timestamp": "2019-06-10T10:55:20.650+0530",
"level": "error",
"type": "websocket-log",
"detail": {
"event": {
"type": "operation",
"detail": {
"request_id": "150e3e6a-e1a7-46ba-a9d4-da6b192a4005",
"operation_id": "1",
"query": {
"variables": {},
"query": "subscription {\n author {\n namex\n }\n}\n"
},
"operation_type": {
"type": "query_err",
"detail": {
"path": "$.selectionSet.author.selectionSet.namex",
"error": "field 'namex' not found in type: 'author'",
"code": "validation-failed"
}
},
"operation_name": null
}
},
"connection_info": {
"websocket_id": "49932ddf-e54d-42c6-bffb-8a57a1c6dcbe",
"jwt_expiry": null,
"msg": null
},
"user_vars": {
"x-hasura-role": "admin"
}
}
}
health-check-log structure
The GraphQL Engine does recurring health checks on data sources and logs the status with other details. This is how the health check log looks like:
- On successful health check
{
"level": "info",
"timestamp": "2022-07-28T12:23:56.555+0530",
"type": "health-check-log",
"detail": {
"source_name": "mssql",
"status": "OK",
"timestamp": "2022-07-28T06:53:56.555Z",
"error": null,
"internal": {
"interval": 5,
"max_retries": 3,
"retry_iteration": 0,
"timeout": 3
}
}
}
- When health check is timed out
{
"level": "warn",
"timestamp": "2022-07-28T12:28:16.165+0530",
"type": "health-check-log",
"detail": {
"source_name": "mssql",
"status": "TIMEOUT",
"timestamp": "2022-07-28T06:58:16.165Z",
"error": null,
"internal": {
"interval": 5,
"max_retries": 3,
"retry_iteration": 3,
"timeout": 3
}
}
}
- When health check results in an error
{
"level": "warn",
"timestamp": "2022-07-28T12:30:06.643+0530",
"type": "health-check-log",
"detail": {
"source_name": "postgres",
"status": "ERROR",
"timestamp": "2022-07-28T07:00:06.643Z",
"error": {
"message": "connection error",
"extra": "connection to server at \"localhost\" (::1), port 6432 failed: Connection refused\n\tIs the server running on that host and accepting TCP/IP connections?\nconnection to server at \"localhost\" (127.0.0.1), port 6432 failed: Connection refused\n\tIs the server running on that host and accepting TCP/IP connections?\n"
},
"internal": {
"interval": 10,
"max_retries": 3,
"retry_iteration": 3,
"timeout": 5
}
}
}
The type
in the log will be health-check-log
and details of the health check will be under detail
key.
The detail
field value is an object contains the following members.
Name | Type | Description |
---|---|---|
source_name | string | The name of the source |
status | HealthCheckStatus string | The health status of the source |
timestamp | string | The timestamp in UTC when the health check is finished |
error | HealthCheckError value | The details of the error |
internal | Internal object | Internals of the health check config |
HealthCheckStatus is a mandatory field whose values are as follows.
Health check status Description Log level OK
Health check succeeded with no errors. info
FAILED
Health check is failed maybe due to bad connection config. warn
TIMEOUT
Health check is timed out. The timeout value is specified in the healch check config warn
ERROR
Health check results in an exception. warn
HealthCheckError contains more information about the health check exception when the status is
ERROR
. For other statuses the value will benull
. Theerror
object contains the following fieldsmessage
: string. A very brief description about the error.extra
: any json. Contains extra and detailed information about the error.
Internal is an object contains the following fields.
interval
: int. Health check interval in seconds.max_retries
: int. Maximum # of retries configured.retry_interation
: int. The iteration on which the health check is succeeded. In case of unsuccessful health check, the retry iteration is same asmax_retries
.retry_interval
: int. The retry interval in seconds.timeout
: int. Health check time out value in seconds.
The GraphQL Engine logs the health check status only when
- the
status
is notOK
- the previous check
status
was notOK
and currentstatus
isOK
Monitoring frameworks
You can integrate the logs emitted by Hasura GraphQL with external monitoring tools for better visibility as per your convenience.
For some examples, see Guides: Integrating with monitoring frameworks