`robot.Rws.Elog` reads the event log of the controller, the same list of events the operator sees on the teach pendant. It is the first place to look when the robot stopped and you do not know why.

The log is split in domains. Each domain keeps its own messages, in a buffer of a fixed size, and the oldest message is dropped when the buffer is full.

## Domains

`GetDomains` lists the domains of the controller with the number of messages each one holds. The number of a domain is what every other method of the service takes as its first argument.

**C# : ElogDomains**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws.Data;

public class ElogDomains
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // Pass a language to get the names of the domains
        ElogDomain[] domains = robot.Rws.Elog.GetDomains("en");

        foreach (ElogDomain item in domains)
        {
            Console.WriteLine($"{item.Number} {item.Name}: {item.MessageCount}/{item.BufferSize}");
        }

        // The counters of one domain, without its name
        ElogDomain domain = robot.Rws.Elog.GetDomain(1);
        Console.WriteLine(domain.MessageCount);
        Console.WriteLine(domain.BufferSize);
        /**/

        robot.Disconnect();
    }
}
```

**Python : ElogDomains**
```python
from underautomation.abb.abb_controller import AbbController

robot = AbbController()
robot.connect("192.168.0.1")

##
# Pass a language to get the names of the domains
domains = robot.rws.elog.get_domains("en")

for item in domains:
    print(f"{item.number} {item.name}: {item.message_count}/{item.buffer_size}")

# The counters of one domain, without its name
domain = robot.rws.elog.get_domain(1)
print(domain.message_count)
print(domain.buffer_size)
##

robot.disconnect()
```

Pass a language code to get the names of the domains, for example `Common`, `Operational` or `Safety`. Without a language the names are null and only the numbers and the counters are read, which is faster. The common domain is always there, the other ones depend on the options installed on the controller.

`GetDomain` reads the counters of one domain. The controller does not report the name there, read it from `GetDomains`.

## Read messages

`GetMessages` returns the messages of one domain. The SDK reads the pages of the answer for you and returns one array, so a domain holding hundreds of messages is read in one call.

**C# : ElogMessages**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws.Data;

public class ElogMessages
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // The 20 most recent messages of the domain 1, with their full text in English
        ElogMessage[] messages = robot.Rws.Elog.GetMessages(1, ElogMessageOrder.NewestFirst, "en", 20);

        foreach (ElogMessage message in messages)
        {
            Console.WriteLine($"{message.Timestamp:yyyy-MM-dd HH:mm:ss} [{message.Type}] {message.Code} {message.Title}");
        }
        /**/

        /**/
        // Titles only, much faster on a domain holding hundreds of messages
        ElogMessage[] titles = robot.Rws.Elog.GetMessageTitles(1, "en", ElogMessageOrder.NewestFirst, 50);

        // The oldest messages first, without any text, so no language is needed
        ElogMessage[] oldest = robot.Rws.Elog.GetMessages(1, ElogMessageOrder.OldestFirst, null, 10);

        foreach (ElogMessage message in oldest)
        {
            Console.WriteLine($"{message.SequenceNumber} {message.Code} {message.Type} {message.SourceName}");
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : ElogMessages**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.elog_message_order import ElogMessageOrder

robot = AbbController()
robot.connect("192.168.0.1")

##
# The 20 most recent messages of the domain 1, with their full text in English
messages = robot.rws.elog.get_messages(1, ElogMessageOrder.NewestFirst, "en", 20)

for message in messages:
    print(f"{message.timestamp} [{message.type}] {message.code} {message.title}")
##

##
# Titles only, much faster on a domain holding hundreds of messages
titles = robot.rws.elog.get_message_titles(1, "en", ElogMessageOrder.NewestFirst, 50)

# The oldest messages first, without any text, so no language is needed
oldest = robot.rws.elog.get_messages(1, ElogMessageOrder.OldestFirst, None, 10)

for message in oldest:
    print(f"{message.sequence_number} {message.code} {message.type} {message.source_name}")
##

robot.disconnect()
```

| Argument   | Effect                                                               |
| ---------- | -------------------------------------------------------------------- |
| `domain`   | Number of the domain, from `GetDomains`                              |
| `order`    | `NewestFirst` or `OldestFirst`                                       |
| `language` | Two letter code of the language of the texts, null to skip the texts |
| `maxCount` | Stops the reading early, which is what a "latest events" view needs  |

Leave `language` null when you only need the code, the severity and the timestamp of each event. The controller then sends much less text, and the texts of `ElogMessage` stay null.

`GetMessageTitles` is the middle ground: it returns the short text of each message and leaves out the long texts and the arguments. On a busy domain it is a lot faster than `GetMessages`.

| `ElogMessageType` | Meaning                                                      |
| ----------------- | ------------------------------------------------------------ |
| `Information`     | State change or informational event                          |
| `Warning`         | Warning event                                                |
| `Error`           | Error event                                                  |
| `Unknown`         | The controller reports a severity this library does not know |

`Code` is the number printed on the teach pendant for that kind of event. `SequenceNumber` identifies the message inside its domain, and the messages are numbered in the order they were logged, so a higher number is a more recent message.

## Read one message

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes. Reading a message from its sequence number alone needs an OmniCore controller

`GetMessage` returns one message with everything the controller knows about it: the long description, the consequences for the robot, the probable causes, the actions to take, and the arguments.

**C# : ElogOneMessage**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws.Data;

public class ElogOneMessage
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // One message of a domain, from its sequence number
        ElogMessage message = robot.Rws.Elog.GetMessage(1, 42, "en");

        Console.WriteLine(message.Title);
        Console.WriteLine(message.Description);
        Console.WriteLine(message.Consequences);
        Console.WriteLine(message.Causes);
        Console.WriteLine(message.Actions);

        // The values the controller substitutes into the text of the message
        foreach (ElogMessageArgument argument in message.Arguments)
        {
            Console.WriteLine($"{argument.Index}: {argument.Value} ({argument.Type})");
        }
        /**/

        /**/
        // OmniCore only: the same message, without naming the domain it belongs to
        ElogMessage direct = robot.Rws.Elog.GetMessageBySequenceNumber(42, "en");
        /**/

        robot.Disconnect();
    }
}
```

**Python : ElogOneMessage**
```python
from underautomation.abb.abb_controller import AbbController

robot = AbbController()
robot.connect("192.168.0.1")

##
# One message of a domain, from its sequence number
message = robot.rws.elog.get_message(1, 42, "en")

print(message.title)
print(message.description)
print(message.consequences)
print(message.causes)
print(message.actions)

# The values the controller substitutes into the text of the message
for argument in message.arguments:
    print(f"{argument.index}: {argument.value} ({argument.type})")
##

##
# OmniCore only: the same message, without naming the domain it belongs to
direct = robot.rws.elog.get_message_by_sequence_number(42, "en")
##

robot.disconnect()
```

The arguments are the values the controller substitutes into the text of the message, for example the name of the task that was started. Each one carries its position in the message, its type as reported by the controller and its value as text.

`GetMessageBySequenceNumber` reads a message without naming its domain. Only an OmniCore exposes it. On an IRC5 the SDK throws an `RwsException` that says to use `GetMessage` with the domain instead.

## Clear and export the log

`ClearMessages` empties one domain, `ClearAllMessages` empties them all. The messages are gone for good, the controller keeps no copy of what it cleared. `ClearAllMessages` leaves the internal domain the controller reserves for itself untouched.

**C# : ElogClear**
```csharp
using UnderAutomation.ABB;

public class ElogClear
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        // Delete every message of one domain
        robot.Rws.Elog.ClearMessages(1);

        // Delete every message of every domain
        robot.Rws.Elog.ClearAllMessages();
        /**/

        /**/
        // Ask the controller to write the whole event log to one file of its own file system.
        // The call returns before the file is complete.
        robot.Rws.Elog.SaveInSystemDumpFormat("$temp/elog.txt");

        // Read the result back with the file service
        string dump = robot.Rws.File.GetFileAsText("$temp/elog.txt");
        /**/

        robot.Disconnect();
    }
}
```

**Python : ElogClear**
```python
from underautomation.abb.abb_controller import AbbController

robot = AbbController()
robot.connect("192.168.0.1")

##
# Delete every message of one domain
robot.rws.elog.clear_messages(1)

# Delete every message of every domain
robot.rws.elog.clear_all_messages()
##

##
# Ask the controller to write the whole event log to one file of its own file system.
# The call returns before the file is complete.
robot.rws.elog.save_in_system_dump_format("$temp/elog.txt")

# Read the result back with the file service
dump = bytes(robot.rws.file.get_file_as_bytes("$temp/elog.txt")).decode("utf-8")
##

robot.disconnect()
```

`SaveInSystemDumpFormat` asks the controller to write the whole event log to one file of its own file system. This is the format ABB support asks for. The controller accepts the request and writes the file afterwards, so the call returns before the file is complete. Read the destination with the [file system service](/abb/documentation/rws-files) to know when it is there, and to bring it back on your PC.

## Try it in the demo application

Everything on this page can be tried without writing code, in the **Logs (RWS)** page of the [demo application](/abb/documentation/demo-app).

![Logs (RWS) page of the ABB SDK demo application](/abb/WinformsScreenshots/RwsELog.jpg)

Its C# source is [RwsELogControl.cs](https://github.com/underautomation/ABB.NET/blob/main/UnderAutomation.ABB.Showcase.Forms/Components/RwsELogControl.cs).

## API reference

**Methods of ElogService**
```csharp
// Deletes every message of every event log domain (synchronous)
void ClearAllMessages();

// Deletes every message of one event log domain (synchronous)
void ClearMessages(int domain);

// Gets the number of messages one event log domain holds and the number it can hold (synchronous)
ElogDomain GetDomain(int domain);

// Gets every event log domain of the controller, with the number of messages each one holds (synchronous)
ElogDomain[] GetDomains(string language = null);

// Gets one message of an event log domain (synchronous)
ElogMessage GetMessage(int domain, int sequenceNumber, string language = null);

// Gets one message from its sequence number alone, without naming the domain it belongs to (synchronous)
ElogMessage GetMessageBySequenceNumber(int sequenceNumber, string language = null);

// Gets the messages held by one event log domain, with their short text only (synchronous)
ElogMessage[] GetMessageTitles(int domain, string language, ElogMessageOrder order = ElogMessageOrder.NewestFirst, int? maxCount = null);

// Gets the messages held by one event log domain (synchronous)
ElogMessage[] GetMessages(int domain, ElogMessageOrder order = ElogMessageOrder.NewestFirst, string language = null, int? maxCount = null);

// Asks the controller to write the whole event log to one file on its own file system (synchronous)
void SaveInSystemDumpFormat(string path);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.ElogDomain**
```csharp
public class ElogDomain {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.ElogDomain" data-throw-if-not-resolved="false"></xref> class
    public ElogDomain()

    // Number of messages the domain can hold before the oldest ones are discarded,
    // null when the controller did not report it
    public int? BufferSize { get; set; }

    // Number of messages currently held by the domain, null when the controller did not report it
    public int? MessageCount { get; set; }

    // Name of the domain, for example "Operational" or "Safety".
    // 
    // <p>Only filled when a language was asked for, null otherwise.</p>
    public string Name { get; set; }

    // Number identifying the domain, which is the value to pass to the methods reading its messages
    public int Number { get; set; }

    // Returns a string representation of this domain
    public override string ToString()
}
```

**Members of Rws.Data.ElogMessage**
```csharp
public class ElogMessage {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.ElogMessage" data-throw-if-not-resolved="false"></xref> class
    public ElogMessage()

    // Text describing the recommended actions. Only filled when a language was asked for.
    public string Actions { get; set; }

    // Number of arguments of the message
    public int ArgumentCount { get; }

    // Values the controller substitutes into the text of the message
    public ElogMessageArgument[] Arguments { get; set; }

    // Text describing the probable causes of the event. Only filled when a language was asked for.
    public string Causes { get; set; }

    // Number identifying the kind of event, the one printed on the teach pendant
    public int? Code { get; set; }

    // Text describing what the event implies for the robot. Only filled when a language was asked for.
    public string Consequences { get; set; }

    // Long text describing what happened. Only filled when a language was asked for.
    public string Description { get; set; }

    // Number of the domain the message belongs to, null when the controller did not report it
    public int? DomainNumber { get; set; }

    // Number identifying the message inside its domain. Messages are numbered in the order
    // they were logged, so a higher number is a more recent message.
    public int? SequenceNumber { get; set; }

    // Part of the controller that logged the message, for example "MC0"
    public string SourceName { get; set; }

    // Moment the event was logged, null when the controller did not report it
    public DateTime? Timestamp { get; set; }

    // Short text of the message. Only filled when a language was asked for.
    public string Title { get; set; }

    // Returns a string representation of this message
    public override string ToString()

    // Severity of the message
    public ElogMessageType Type { get; set; }
}
```

**Members of Rws.Data.ElogMessageArgument**
```csharp
public class ElogMessageArgument {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.ElogMessageArgument" data-throw-if-not-resolved="false"></xref> class
    public ElogMessageArgument()

    // Position of the argument in the message, starting at 1
    public int Index { get; set; }

    // Returns a string representation of this argument
    public override string ToString()

    // Type of the argument reported by the controller, for example "string", "long" or "float"
    public string Type { get; set; }

    // Value of the argument, always as text
    public string Value { get; set; }
}
```

**Members of Rws.Data.ElogMessageType**
```csharp
public enum ElogMessageType {
    // Error event
    Error = 3

    // State change, or informational event
    Information = 1

    // The message type could not be determined
    Unknown = 0

    // Warning event
    Warning = 2
}
```

**Members of Rws.Data.ElogMessageOrder**
```csharp
public enum ElogMessageOrder {
    // Most recent message first
    NewestFirst = 0

    // Oldest message first
    OldestFirst = 1
}
```