Skip to content

Latest commit

 

History

History
230 lines (198 loc) · 6.88 KB

oe_log_message()_callback_proposal.md

File metadata and controls

230 lines (198 loc) · 6.88 KB

Proposal of Public Callback API for oe_log_message()

Motivation

This document describes the proposed public callback API for oe_log_message().

There are several problems with the current logging mechanism that prevent end users from making full use of logging:

  • Message can't be redirected to different destinations on demand.
  • Lacking of a method for end users to customize messages.
  • It doesn't have a mechanism to communicate between the host application and oe_log_message() such that end users can't pass more information to the message function.

The callback API is supposed to solve such problems.

User Experience

By adding this public API, a user can customize the output of the log message:

  • The output destination can be stdout, stderr, or any specified files that the destination is adjustable during runtime.
  • The message can be adjusted according to the log level.
  • The user can get at least as much information about the message as the internal API. For example, log time, enclave or host side, and log level.
  • Finally, the user can define a function to customize the log message and can designate the funtion to process all the log messages.

Specification

Several necessary items should be added to a public header file "trace.h".

  • The message function declaration:
typedef void (*oe_log_callback_t)(
    void* context,
    bool is_enclave,
    struct tm* t,
    long int usecs,
    oe_log_level_t level,
    uint64_t host_thread_id,
    const char* message);
  • The function to set up the callback:
oe_result_t oe_log_set_callback(void* context, oe_log_callback_t callback)
  • Since log level is exposed to end users, the related declarations should also be exposed:
typedef enum _oe_log_level
{
    OE_LOG_LEVEL_NONE = 0,
    OE_LOG_LEVEL_FATAL,
    OE_LOG_LEVEL_ERROR,
    OE_LOG_LEVEL_WARNING,
    OE_LOG_LEVEL_INFO,
    OE_LOG_LEVEL_VERBOSE,
    OE_LOG_LEVEL_MAX
} oe_log_level_t;

extern const char* const oe_log_level_strings[OE_LOG_LEVEL_MAX];

To avoid multiple definition errors, these items may be placed at a separate header file "log.h".

By including the public header file, the end user can perform customized logging.

Example

Since this API just provides the log callback feature to host applications, here the sample just shows the necessary lines of code at the host side:

#include <openenclave/host.h>
#include <openenclave/trace.h>
#include <stdio.h>

// Include the untrusted log_callback header that is generated
// during the build. This file is generated by calling the
// sdk tool oeedger8r against the log_callback.edl file.
#include "log_callback_u.h"

bool check_simulate_opt(int* argc, const char* argv[])
{
    for (int i = 0; i < *argc; i++)
    {
        if (strcmp(argv[i], "--simulate") == 0)
        {
            fprintf(stdout, "Running in simulation mode\n");
            memmove(&argv[i], &argv[i + 1], (*argc - i) * sizeof(char*));
            (*argc)--;
            return true;
        }
    }
    return false;
}

// This is the function that the enclave will call back into to
// print a message.
void host_hello()
{
    fprintf(stdout, "Enclave called into host to print: Hello!\n");
}

void customized_log(
    void* context,
    bool is_enclave,
    const struct tm* t,
    long int usecs,
    oe_log_level_t level,
    uint64_t host_thread_id,
    const char* message)
{
    char time[25];
    strftime(time, sizeof(time), "%Y-%m-%dT%H:%M:%S%z", t);

    FILE* log_file = NULL;
    if (level >= OE_LOG_LEVEL_WARNING)
    {
        log_file = (FILE*)context;
    }
    else
    {
        log_file = stderr;
    }

    fprintf(
        log_file,
        "%s.%06ld, %s, %s, %llx, %s",
        time,
        usecs,
        (is_enclave ? "E" : "H"),
        oe_log_level_strings[level],
        host_thread_id,
        message);
}

int main(int argc, const char* argv[])
{
    FILE* out_file = fopen("./oe_out.txt", "w");
    oe_log_set_callback((void*)out_file, customized_log);

    oe_result_t result;
    int ret = 1;
    oe_enclave_t* enclave = NULL;

    uint32_t flags = OE_ENCLAVE_FLAG_DEBUG;
    if (check_simulate_opt(&argc, argv))
    {
        flags |= OE_ENCLAVE_FLAG_SIMULATE;
    }

    if (argc != 2)
    {
        fprintf(
            stderr, "Usage: %s enclave_image_path [ --simulate  ]\n", argv[0]);
        goto exit;
    }

    // Create the enclave
    result = oe_create_log_callback_enclave(
        argv[1], OE_ENCLAVE_TYPE_AUTO, flags, NULL, 0, &enclave);
    if (result != OE_OK)
    {
        fprintf(
            stderr,
            "oe_create_log_callback_enclave(): result=%u (%s)\n",
            result,
            oe_result_str(result));
        goto exit;
    }

    // Call into the enclave
    result = enclave_hello(enclave);
    if (result != OE_OK)
    {
        fprintf(
            stderr,
            "calling into enclave_hello failed: result=%u (%s)\n",
            result,
            oe_result_str(result));
        goto exit;
    }
    else
    {
        fprintf(stdout, "Please check ./oe_out.txt for the redirected logs.\n");
    }

    ret = 0;

exit:
    // Clean up the enclave if we created one
    if (out_file)
        fclose(out_file);

    if (enclave)
        oe_terminate_enclave(enclave);

    return ret;
}

First the user may prepare a customized log processing function "customized_log()", which must follow the function type "*oe_log_callback_t". All these parameters are some information about logging, but the user has the full control to make use of them. The timestamp information, which is not printable, should be converted to a string. Here the printing format "%Y-%m-%dT%H:%M:%S%z" is a good format that it follows the ISO 8601 rules. After that, the printing destination is chosen, based on the log level. In principle, it's better not to mix low priority messages with high priority messages lest important messages be flooded by tedious messages. Finally, all the information and messages will be written to the specified destination.

When oe_log_message() is called, the most recently registered custom log function will be invoked. Obviously, if it is registered before starting the enclave, all the logs are affected.

By passing in the specified log file, the context is assigned as a pointer to the desired file "./oe_out.txt". In this case the context is used as the logging destination. Besides that context can be casted back as any desired type, since type of context is "void*".

In the user's expectation, log messages with level equal or greater than OE_LOG_LEVEL_WARNING will be writen to "./oe_out.txt", and others will be writen to "stderr". The typical output is as following:

alvin@wechen3-u18-2:~/openenclave/build/samples/log_callback$ cat oe_out.txt
2020-08-19T08:48:37-0700.025528, H, INFO, 7f079bd8bb80, Processor supports AVX instructions [/home/alvin/openenclave/host/sgx/linux/xstate.c:_is_xgetbv_supported:33]
alvin@wechen3-u18-2:~/openenclave/build/samples/log_callback$