Logging messages in Scala

You can log messages from a function or procedure handler written in Scala by using the SLF4J API. When you’ve set up an event table to store log entries, Snowflake stores log entries generated by your handler code in the table.

You can use the SLF4J API included with the Snowflake Telemetry library included on Snowflake. To do so, include the following value in the PACKAGES clause when you create the function or procedure: com.snowflake:telemetry:latest.

For information on including the Telemetry library when packaging your code with Maven, see Setting up your Java and Scala environment to use the Telemetry class.

Note

Using the Snowflake Telemetry Library adds other libraries to your function or procedure’s execution environment. For more information, see Snowflake telemetry package dependencies.

Note

SLF4J does not support logging messages at the FATAL level. For handlers written in Java or Scala, the FATAL level is treated as the ERROR level.

For example, if you set the LOG_LEVEL parameter to FATAL, ERROR-level messages from a Java or Scala handler are ingested.

For general information about setting up logging and retrieving messages in Snowflake, see Logging messages from functions and procedures.

Before logging from code, you must:

Adding custom attributes

When you create a log entry, you can add your own attributes in key-value pairs. Snowflake saves these custom attributes to the event table’s RECORD_ATTRIBUTES column.

To add custom attributes, call methods of the slf4j fluent API, such as Logger.atInfo and Logger.atError. Use these methods to set key-value pairs in the log entry. Each returns an org.slf4j.spi.LoggingEventBuilder, which you can use to set the log message.

Code in the following example logs a message “Logging with attributes” to the event table’s VALUE column. It also adds a custom attribute to the RECORD_ATTRIBUTES column.

CREATE OR REPLACE PROCEDURE do_logging_scala()
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = '2.12'
PACKAGES=('com.snowflake:telemetry:latest', 'com.snowflake:snowpark:latest')
HANDLER = 'ScalaLoggingHandler.doThings'
AS
$$
  import org.slf4j.Logger
  import org.slf4j.LoggerFactory
  import com.snowflake.snowpark.Session

  class ScalaLoggingHandler {
    private val logger: Logger = LoggerFactory.getLogger(getClass)

    def doThings(session: Session): String = {
      logger.atInfo().addKeyValue("custom1", "value1").setMessage("Logging with attributes").log();
      return "SUCCESS"
    }
  }
$$;
Copy

Output of this Logger.atInfo call appears in the event table as follows. Note that the RECORD_ATTRIBUTES column will include attributes that Snowflake adds automatically.

------------------------------------------------------------------
| VALUE                     | RECORD_ATTRIBUTES                  |
------------------------------------------------------------------
| "Logging with attributes" | {                                  |
|                           |   "custom1": "value1",             |
|                           |   "thread.name": "Thread-5"        |
|                           | }                                  |
------------------------------------------------------------------

Scala example

Code in the following example imports references the Snowflake Telemetry library and from it gets a logger. It logs a message at the INFO level. It also logs an error for an exception.

For more information about the methods you can use to log at specific levels, see SLF4J methods.

CREATE OR REPLACE PROCEDURE do_logging()
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = '2.12'
PACKAGES=('com.snowflake:snowpark:latest', 'com.snowflake:telemetry:latest')
HANDLER = 'ScalaLoggingHandler.doThings'
AS
$$
  import org.slf4j.Logger
  import org.slf4j.LoggerFactory
  import com.snowflake.snowpark.Session

  class ScalaLoggingHandler {
    private val logger: Logger = LoggerFactory.getLogger(getClass)

    logger.info("Logging from within the Scala constructor.")

    def doThings(session: Session): String = {
      logger.info("Logging from Scala method start.")

      try {
        throwException
      } catch {
        case e: Exception => logger.error("Logging an error from Scala handler: " + e.getMessage())
        return "ERROR"
      }
      return "SUCCESS"
    }

    // Simulate a thrown exception to catch.
    @throws(classOf[Exception])
    private def throwException = {
      throw new Exception("Something went wrong.")
    }
  }
$$
;
Copy

You can access log messages by executing a SELECT command on the event table. For more information, see Viewing log messages.

Code in the following example queries the event table where the log messages are stored. The query reports on the severity and message of each log entry from the handler class.

SET event_table_name='my_db.public.my_event_table';

SELECT
  RECORD['severity_text'] AS SEVERITY,
  VALUE AS MESSAGE
FROM
  IDENTIFIER($event_table_name)
WHERE
  SCOPE['name'] = 'ScalaLoggingHandler'
  AND RECORD_TYPE = 'LOG';
Copy

The preceding example generates the following output.

---------------------------------------------------------------------------
| SEVERITY | MESSAGE                                                      |
---------------------------------------------------------------------------
| "INFO"   | "Logging from within the Scala constructor."                 |
---------------------------------------------------------------------------
| "INFO"   | "Logging from Scala method start."                           |
---------------------------------------------------------------------------
| "ERROR"  | "Logging an error from Scala handler: Something went wrong." |
---------------------------------------------------------------------------