Skip to content

Messages: programming

This page covers sending and reading messages from code.

For the concepts and day-to-day use, see Messages: overview.

For full descriptions of the programming interfaces (e.g. all the optional parameters that aren't mentioned here), see the API reference for C++ (cm_msg_xxx), Python (midas.client), JavaScript (midas.js) and the JSON-RPC methods.

Implementation details

Any midas program can write messages, and they get written to log files and/or the SYSMSG event buffer.

The program sending the message is the one that writes it to the log file (unless the program is running on a remote host, in which case mserver writes it to the file). This means that the midas logger (mlogger) does not need to be running for the message system to work.

Implementation in cm_msg()

Messages are not written immediately. Instead they are buffered and written/flushed when the program next handles its periodic tasks. Each program opens the log file in "append" mode, writes its messages, then closes the file. Each message is written atomically, so multiple programs can have the log file open at the same time, without needing a semaphore to avoid collisions.

Messages are also sent as midas events, and use the SYSMSG memory buffer. Messages from all facilities use the same buffer. See the data format page for details about how message data is encoded into a midas event. The timestamp is the time that cm_msg() etc was called, not the time it was flushed.

Log levels

The python interface currently only supports info and error messages (via the is_error argument to MidasClient.msg()). C++ and Javascript support all message types.

Message type C++ name Supported in python Javascript name
Error MT_ERROR ✔ MT_ERROR
Info MT_INFO ✔ MT_INFO
Debug MT_DEBUG MT_DEBUG
User MT_USER MT_USER
Log MT_LOG MT_LOG
Talk MT_TALK MT_TALK
Call MT_CALL MT_CALL

Note that debug messages are only sent to the SYSMSG buffer, not to the midas.log file. This is handy if you produce lots of message and don't want to flood the message file. Plus it does not change the timing of your app, since the SYSMSG buffer is much faster than writing to a file.

What actually gets logged

Each entry in the log file is a single line, containing the following information:

Error messages User messages All other messages
Timestamp ✔ ✔ ✔
Program name ✔ ✔
User's name ✔
Message type ✔ ✔ ✔
Source code file name ✔
Source code line number ✔
Source code function name ✔
Message text ✔ ✔ ✔

Below are three example messages:

# INFO message is fairly simple
12:47:03.331 2026/04/23 [py,INFO] Program py on host localhost stopped

# ERROR message contains lots of details
12:47:03.331 2026/04/23 [py,ERROR] [odb.cxx:4708:db_get_value,ERROR] buffer size 32 too small, data size 10x64, truncated for key "/pytest/strarr"

# USER message contains the user's name instead of a program name
15:11:23.267 2026/04/23 [ben,USER] Hello!

The timestamp format is %H:%M:%S.xxx %Y/%m/%d, where xxx represents milliseconds.

Note that it is also possible to log messages in midas data files.

Sending messages

You should avoid multi-line messages when sending messages. This is because the log file format is very simple, and some tools fail to parse it correctly if there are spurious new-line characters.

// The MERROR macro is a shorthand for filling the first 3 arguments of
// cm_msg - the type, filename and line number.
// There are equivalents for MINFO, MDEBUG etc.

INT status = cm_msg(MERROR, __FUNCTION__, "Hello!");

// You can also use printf-style arguments:
int x = 123;
status = cm_msg(MERROR, __FUNCTION__, "Hello, x is %d", x);

// You can also manually specify the function name if that is more useful than
// the "real" function that's sending the message:
INT status = cm_msg(MERROR, "some_function", "Hello!");
client.msg("Hello!", is_error=True)
mjsonrpc_cm_msg("Hello!", MT_ERROR).then(function(rpc) {
  if (rpc.result.status == 1) {
    // SUCCESS!
  }
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
# Parameters are <facility> <type> <name> <msg>.
# Types are the english words in upper case (ERROR, INFO, USER etc)
odbedit -c 'msg midas USER my_name "Hello!"'

Getting a list of message facilities

std::vector<std::string> facilities;
INT status = cm_msg_facilities(&facilities);
facilities = client.get_message_facilities()
# faciltiies is a list of strings, e.g. ["midas", "pysequencer"]
mjsonrpc_call("cm_msg_facilities", {}).then(function(rpc) {
  let facilities = rpc.result.facilities
  // faciltiies is an Array of strings, e.g. ["midas", "pysequencer"]
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});

Reading messages

The interface for reading messages is most suited to "scrolling/paging" situations, where you look at recent messages, then ask for the N most recent before those. So you ask for messages before a certain timestamp.

The interfaces below do not parse the lines in the log file, except for extracting a UNIX timestamp from the human-readable date.

For example, if your log file contains this line:

15:40:02.618 2026/04/23 [py,LOG] Program py on host localhost started

the code interfaces will return:

1776984002 15:40:02.618 2026/04/23 [py,LOG] Program py on host localhost started

(note the extra timestamp at the start).

The interfaces also require you to specify the minimum number of messages to return. The algorithm used is:

  • Search until we find min_messages
  • If there are more messages with the same UNIX timestamp (to 1s resolution), include those messages as well.
  • If log files are being split by date, and we've had to look back through several files without finding anything, give up.
std::string facility = "midas";
time_t timestamp = time(NULL); //(1)!
int min_messages = 100;
char *messages = NULL;
int num_messages = 0;

INT status = cm_msg_retrieve2(facility.c_str(), timestamp, min_messages, &messages, &num_messages);

if (messages) {
  for (int i = 0; i < num_messages; i++) {
    printf("Message %d: %s", i, messages[i]); //(2)!
  }

  free(messages);
}
  1. You can also provide a timestamp of 0 to mean "now"
  2. The message includes a trailing new-line character
before = datetime.datetime.now() #(1)!

messages = client.get_recent_messages(before, min_messages=100, facility="midas")

for idx, message in enumerate(messages):
  print(f"Message {idx}: {message}")
  1. Can also use a number (as a UNIX timestamp), on None/0 (to mean "now")
let params = Object();
params.facility = "midas";
params.min_messages = 100;
params.time = 0; //(1)!

mjsonrpc_call("cm_msg_retrieve", params).then(function(rpc) {
  if (rpc.result.status == 1) {
    let messages = rpc.result.messages.split("\n");

    for (let i = 0; i < messages.length; i++) {
      console.log("Message " + i + ": " + messages[i]);
    }
  }
}).catch(function(error) {
  mjsonrpc_error_alert(error);
});
  1. Can be a UNIX timestamp, or special value 0 meaning "now"
odbedit -c 'old 100' #(1)!
  1. Only supports getting the most recent N messages, and only from the midas facility.

Getting notified about new messages

In C++ and Python you can request that a function gets called whenever a message is written to the log.

void my_callback(HNDLE hBuf, HNDLE id, EVENT_HEADER *header, void *message) {
  int msg_type = header->trigger_mask;
  uint32_t timestamp = header->time_stamp;

  if (msg_type == MT_ERROR) {
    printf("Received an ERROR message at %u: %s", timestamp, message);
  } else if (msg_type == MT_INFO) {
    printf("Received an INFO message at %u: %s", timestamp, message);
  } else {
    // Ignore
  }
}

// Later, in main program:
cm_msg_register(my_callback);
def my_callback(client, msg, timestamp, msg_type):
  if msg_type == midas.MT_ERROR:
    print(f"Received an ERROR message at {timestamp}: {msg}") #(1)!
  else:
    print(f"Received an INFO message at {timestamp}: {msg}")

# Later, in main program:
client.register_message_callback(my_callback, only_message_types=[midas.MT_INFO, midas.MT_ERROR])
  1. timestamp is a datetime.datetime object; msg is a string.