# Logger  ## Logging from actions  Every action will be passed a `logger` object in the incoming arguments. The `logger` conforms to the and will output log statements viewable in Gadget's Logs. ```typescript export const run: ActionRun = async ({ logger, api }) => { const newRecord = await api.otherModel.create({ title: "new title" }); logger.info({ result: newRecord }, "created otherModel record"); // log entry will show up in the Logs }; ``` ## Logging from HTTP routes  Every HTTP route will be passed a `request.logger` object on the incoming `request` object which can be used during the request to write logs. The logger adheres to the and displays log statements in Gadget's Logs. ```typescript import { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply, logger }) => { logger.info({ ip: request.ip }, "requester ip"); // log entry will show up in the Logs await reply.code(200).send("route response"); }; ``` ## Logging from React Router and Remix loaders  When using [React Router](https://docs.gadget-canary.xyz/guides/frontend/react-router-in-gadget) or [Remix](https://docs.gadget-canary.xyz/guides/frontend/remix-in-gadget) in framework mode, your `loader` functions have access to a `logger` object through the `context` parameter. This logger is the same structured logger available in Gadget actions and HTTP routes. ```tsx import type { Route } from "./+types/_user.todos"; export const loader = async ({ context }: Route.LoaderArgs) => { context.logger.info({ userId: context.session?.id }, "fetching todos"); const todos = await context.api.todo.findMany(); context.logger.debug({ count: todos.length }, "todos fetched successfully"); return { todos }; }; ``` ## `console.log`  Gadget supports `console.log` statements for logging in your application and will show `console.log` messages in the Log's at the `info` log level. ```typescript export const run: ActionRun = async ({ logger }) => { const result = await someAPICall(); // works but is discouraged, will log just this message to your logger console.log(`finished API call: ${result.statusCode}`); // works well and is encouraged, will log the message with surrounding context like the action and model, and is more performant logger.info({ statusCode: result.statusCode }, "finished API call"); }; ``` Using the structured `logger` object is recommended over using `console.log` for better performance, and to allow Gadget to add more context to your log messages. ## `logger` API  Log messages are sent to the logger by calling one of the logging methods with an optional structured data object and a string message. ```typescript // log a plain old string message at the info level logger.info("Hello world"); // log a structured object and a message at the info level logger.info({ key: "value", foo: 42, record: someRecord }, "making progress"); // log an error at the error level try { "foo" in null; } catch (error) { logger.error({ error }); } ``` The default log levels (and associated log functions) are `trace`, `debug`, `info`, `warn`, `error`, and `fatal`. The passed `logger` object is an instance of a [`pino.Logger`](https://getpino.io/#/docs/api) managed by the Gadget platform. View the [Pino logger docs](https://getpino.io/#/docs/api) for full API documentation. ### Passing structured data  An object can optionally be supplied as the first parameter to log messages. Each key and value of the object is stringified and written to your application's logs as JSON. ```typescript logger.info({ value: { foo: "bar" } }); ``` Log calls at any level can also pass `Error` objects to get an easy-to-digest log entry in the Log Viewer for the error, including it's stack trace and any other associated details. To log `Error` objects, pass the object at the `error` key of the structured data: ```typescript const error = new Error("something went wrong"); logger.warn({ error }, "error encountered, continuing"); ``` # Viewing logs  Gadget logs all events in your application. Logs are queryable using [LogQL](https://grafana.com/docs/loki/latest/logql/log_queries/), a powerful language for searching through logs. Gadget apps utilize structured logging, where logs are JSON objects that can contain a string message _and also_ key-value pairs of other data. `console.log` works like it might in the browser or Node.js in other JavaScript environments, but if you want to log structured data, you can use the structured `logger` object passed to your action files or `request.log` passed to HTTP routes. The default log retention period is 30 days. If you'd like to increase this, please reach out to support. ## Querying for a string  To search your logs for a specific string, use the `|=` operator: ```logql {level="info|warn|error", environment_id="{YOUR_ENVIRONMENT_ID"} |= "some string" ``` Gadget will return all logs containing this string over the searched time range. ## Excluding a string  To search your logs but exclude a specific string, use the `!=` operator: ```logql {level="info|warn|error", environment_id="{YOUR_ENVIRONMENT_ID"} != "some string" ``` ## Querying for key/value pairs  To search your logs for structured entries where a particular key has been set to a particular value, add a `| json` filter and then use the `|` operator to search for a particular key value pair. ```logql {level="info|warn|error", environment_id="{YOUR_ENVIRONMENT_ID"} | json | someKey="some value" ``` Key/value search parameters must be preceded by the `| json |` operator to parse all the keys and values in the structured logs. You can search for multiple keys and values simultaneously using the `and` and `or` operators. ```logql {level="info|warn|error", environment_id="{YOUR_ENVIRONMENT_ID"} | json | someKey="some value" or someKey="some other value" # or {level="info|warn|error", environment_id="{YOUR_ENVIRONMENT_ID"} | json | someKey="some value" and anotherKey="some other value" ``` ## Querying for a particular `trace_id`  To search your logs for a specific `trace_id` (see ), use the `json` filter and then filter on the `trace_id`. ```logql {level="info|warn|error", environment_id="{YOUR_ENVIRONMENT_ID"} | json | trace_id="abcde12345" ``` Gadget will return all logs tagged with this trace ID over the searched time range. If nothing is found, you may need to expand your time ranger further to find when a particular trace was executed. ## Stream selectors  Gadget logs are queryable via [LogQL](https://grafana.com/docs/loki/latest/logql/log_queries/), which always requires a _stream selector_ within the log query. The stream selector is the part of the query at the beginning that looks like `{level="info|warn|error"}`. Gadget includes a default stream selector in all generated log queries. You can modify the stream selector, but you can't omit it. If you need to restore the default stream selector in your query, click the **Reset** button in the query bar. ## Platform Logs  Gadget emits log entries as a platform in order to keep you informed about what work Gadget is conducting within your application. Gadget emits log entries for: * Incoming requests and responses for HTTP routes * Incoming requests and responses for GraphQL API requests * Incoming webhooks for Connections including the [Shopify connection](https://docs.gadget-canary.xyz/guides/plugins/shopify) * Sync executions for the Shopify connection * Shopify connection shop installation and uninstallation ## Trace IDs  Gadget attaches a unique identifier to request called a trace ID. Every log emitted by any piece of code during that request will have the same trace ID marked on it, which allows you to find all the logs for one given request in your Logger. ### Viewing all the logs for a single trace  To view the logs for one particular request through your application, and filter out all other requests, click the blue `trace_id` label in the logger for any log entry: When you click a trace ID, the logger will adjust the log search query to filter out everything except logs for this particular trace. You can then use the LogQL query language to filter the logs further. ### Getting the trace ID for a request  Gadget replies to each request with the trace ID in the `x-trace-id` header. If you use your browser's Developer Tools and look in the Network tab at a request made to the Gadget platform, you can find an `x-trace-id` response header showing the trace ID that Gadget assigned that request. For requests to your app's GraphQL API, Gadget will also include an `extensions` property in the JSON response containing the `trace_id` for easy copy/pasting, as well as a direct link to the logs within your app for that trace. Requests made from any client will include this data, including your app's frontend and the Gadget GraphQL Playground. For example, this request made in the playground includes a link to the logger: The trace ID and logs link are not sensitive data and are sent with every response. Only developers with access to your application's editor will be able to view the logs for a given trace ID. ### Sending the trace ID as part of a response  If you want to include the trace ID in your response for debugging purposes or displaying it to the end user, you can access the traceId using the `trace` and `context` objects from the `@opentelemetry/api` package. Here's an example of how you might include the trace ID in a route's response: ```typescript import { RouteHandler } from "gadget-server"; import { trace, context } from "@opentelemetry/api"; function getCurrentTraceId() { const spanContext = trace.getSpanContext(context.active()); return spanContext?.traceId; } /** * Route handler for GET hello * * @type { RouteHandler } route handler - see: https://docs.gadget.dev/guides/http-routes/route-configuration#route-context */ const route = async ({ request, reply, api, logger, connections }) => { // This route file will respond to an http request -- feel free to edit or change it! // For more information on HTTP routes in Gadget applications, see https://docs.gadget.dev/guides/http-routes await reply .type("application/json") .send({ hello: "world", traceId: getCurrentTraceId() }); }; export default route; ```