Displaying Colored Logs with Search Highlighting in Angular 6

In many applications, logging plays an essential role in debugging and monitoring. A well-formatted, readable log view can significantly improve the user experience for developers and system administrators. In this article, we'll explore how to display logs in a colored, formatted manner within an Angular 6 project. We’ll also implement functionality to highlight specific search terms within the logs.

We will achieve this using Prism.js for syntax highlighting and Angular's built-in features such as data binding, DOM sanitization, and more.

1. Project Setup

Before we dive into the code, let's make sure we have everything set up correctly. We'll need to install the Prism.js dependency for syntax highlighting, along with its language-specific components.

npm install prismjs

In your component, import Prism.js and the necessary components:

import * as Prism from 'prismjs';
import 'prismjs/components/prism-log'; // Prism's Log language
import 'prismjs/prism';

2. Component HTML Template

We’ll create a simple HTML structure to display the logs. Using Angular’s *ngIf directive, we'll conditionally render the logs if they are available, and wrap them in a <pre> tag to preserve the formatting.

<div *ngIf="logsByLine && logsByLine.length > 0" style="height: 80vh;">
  <pre class="language-log w-100 scroll" style="height: 100%; overflow-y: auto;">
    <code [ngClass]="{'white-space-pre-wrap': isTextWrapped}" 
          class="language-log" 
          [innerHTML]="getHighlightedContent()"></code>
  </pre>
</div>
  • The *ngIf ensures the logs are only displayed when there is data to show.

  • We apply syntax highlighting by using the language-log class and leverage Prism.js to handle the syntax highlighting.

  • The getHighlightedContent() method will generate the highlighted log content dynamically.

We also add a search input field that allows users to filter logs based on a keyword. This will allow us to highlight specific search terms within the logs.

<div class="col-md-4">
  <input type="text" style="margin-top: 2rem;" 
         class="form-control" name="searchTerm" 
         placeholder="Filter logs" [(ngModel)]="searchTerm">
</div>

3. Component TypeScript Logic

In the TypeScript file, we have several functions to handle log parsing, syntax highlighting, and searching.

Handling Logs

showLogs() {
  const lines: number = Number(this.numOfLines);
  if (isNaN(lines)) {
    this.jhiAlertService.error('Invalid input: Number of lines must be a valid number', null, null);
    return;
  }
  this.logsByLine = '';
  this.metricsService.showLogs(lines).subscribe((res: any) => {
    this.logsByLine = res.join('\n');
    this.callLogsTimeInterval();
  });
}

The showLogs() function fetches logs and stores them in the logsByLine variable. Once the logs are fetched, we call callLogsTimeInterval() to handle any periodic updates (optional).

Highlighting Log Content

getHighlightedContent() {
  if (!this.logsByLine) return '';
  
  let highlighted = Prism.highlight(this.logsByLine, Prism.languages.log, 'log');
  
  // Highlight ERROR lines with a special background
  highlighted = highlighted.split('\n').map(line => {
    if (line.includes('ERROR')) {
      return `<div class="log-line error">${line}</div>`;
    }
    return `<div class="log-line">${line}</div>`;
  }).join('');
  
  // Highlight the search term if provided
  if (this.searchTerm) {
    const regex = new RegExp(this.searchTerm, 'gi');
    highlighted = highlighted.replace(regex, match => `<mark>${match}</mark>`);
  }

  return this.sanitizer.bypassSecurityTrustHtml(highlighted);
}

The getHighlightedContent() method does the following:

  1. Uses Prism.js to highlight the logs.

  2. Applies a background color to log lines that contain the keyword ERROR.

  3. Highlights any search terms that match the user's input. This is done using a regular expression to replace matching terms with a <mark> HTML tag, which we can style further.

Handling Search Term Change

onSearchTermChange(searchTerm: string) {
  this.searchTerm = searchTerm;
}

Whenever the search term changes (due to user input in the search field), this method updates the searchTerm variable.

4. View Encapsulation and Sanitization

To ensure that our component styles don’t interfere with other components in the application, we use ViewEncapsulation.None. This allows the styles defined in the component to be global. Additionally, we use Angular’s DomSanitizer to securely bypass Angular’s built-in sanitization for HTML content, which is required when injecting dynamic content into the DOM.

encapsulation: ViewEncapsulation.None, // Disable encapsulation for global styles

highlightedLogs: SafeHtml; // Holds the highlighted log content

searchTerm: string = ''; // Stores the search term entered by the user
logsTimeInterval: any; // Optional time interval for periodic log fetching

private sanitizer: DomSanitizer; // Ensures that dynamically injected HTML is safe
  • ViewEncapsulation.None: Disables view encapsulation, which allows the styles to leak outside the component. This is useful for global styles like syntax highlighting.

  • SafeHtml: The type of highlightedLogs, ensuring that HTML content injected dynamically into the DOM is safe.

  • DomSanitizer: This service is used to bypass Angular’s built-in sanitization when working with dynamic HTML content to prevent potential security issues (e.g., XSS attacks).

5. Component Styles (SCSS)

Now, let’s add some styles to make the log display more readable.

.card { margin-bottom: 1rem; }
.card-header { background-color: #f8f9fa; font-weight: bold; }
.alert { padding: 0.5rem 1rem; }
.btn-primary { min-width: 120px; }
.fa-sync-alt { margin-right: 8px; }
.fa-spin { animation: fa-spin 2s infinite linear; }

@keyframes fa-spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.gap-top { margin-top: 5rem; }

.white-space-pre-wrap { white-space: pre-wrap !important; }

mark { 
  background-color: rgb(244, 244, 112) !important; 
  color: black;
}

.log-line { line-height: 1.5; }

.log-line.error { 
  background-color: rgba(255, 0, 0, 0.1); 
}

.token.level { font-weight: bold; }
.token.level.info { color: #61afef; }
.token.level.error { color: #e06c75; }
.token.level.warn { color: #e5c07b; }
.token.level.debug { color: #98c379; }

code[class="language-"], pre[class*="language-"] { 
  color: #ccc;
  background: none;
  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
  font-size: 1em;
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  line-height: 1.5;
  tab-size: 4;
  hyphens: none;
}

In this SCSS:

  • .log-line.error styles the lines with the ERROR keyword, giving them a red background.

  • .white-space-pre-wrap ensures that long lines of text wrap properly.

  • We customize the appearance of highlighted code using .token.level classes, adding distinct colors for different log levels like info, error, warn, and debug.

6. Conclusion

By integrating Prism.js with Angular 6, we can create a powerful, visually appealing log viewer that highlights important log entries (like errors) and allows the user to search through logs effectively. The flexibility of this approach allows you to extend it further with features like pagination, more sophisticated search functionality, and real-time log updates.

This solution combines Angular's reactive features with Prism.js for clean, readable log display and enhanced functionality. You can use it in your Angular 6 projects and even customize it to fit the specific needs of your logging system.

Key Features:

  • ViewEncapsulation.None for global styling.

  • SafeHtml and DomSanitizer for secure dynamic HTML content injection.

  • Real-time search highlighting and syntax highlighting using Prism.js.

Last updated