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.
Additionally, oe_set_host_log_level() and oe_set_enclave_log_level() APIs allow applications to dynamically change the log level verbosity of host and enclave generated logs, respectively.
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.
To let the application control the verbosity of the logs generated, depending on runtime events within the application, eg. failure of an ecall, the APIs oe_set_host_log_level() and oe_set_enclave_log_level() are introduced.
oe_set_host_log_level(oe_log_level_t log_level)
allows applications to dynamically modify the verbosity of logs captured from host.
- This API only modifies the verbosity of logs generated by the host. This does not affect enclave log level verbosity.
- It can be called with or without logging callback set and is thread safe. There is no limit on number of calls per application.
oe_set_enclave_log_level(oe_enclave_t* enclave, oe_log_level_t log_level)
allows applications to dynamically modify the verbosity of logs captured from the enclave
.
- This API can be called only from the host application. To control enclave log level from within enclaves, consider using enclave logging callbacks.
- This API can be used functionally only after enclave creation and before enclave termination for an enclave. An application can call this API any number of times, for any number of enclaves. There is no limit on number of calls.
- This API will only set enclave log level verbosity to be the same or lower as host log level verbosity. Eg. If host log level = WARNING, calling oe_set_enclave_log_level(enclave, OE_LOG_LEVEL_INFO) will fail with OE_CONSTRAINT_FAILED.
- On Windows, calling this API after enclave termination will result in segmentation fault (issue #4604)
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".
- The function to modify the host log level verbosity dynamically:
oe_result_t oe_set_host_log_level(oe_log_level_t log_level)
- The function to modify the enclave log level verbosity dynamically:
oe_result_t oe_set_enclave_log_level(oe_enclave_t* enclave, oe_log_level_t log_level)
By including the public header file, the end user can perform customized logging.
Since the callback 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$
The tests log/set_host_log_level and log/set_enclave_log_level provide example of using APIs oe_set_host_log_level
and oe_set_enclave_log_level
.