You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Angular项目中基于Angular Material实现类Kibana日志高级搜索栏?

实现Angular版Kibana风格高级日志搜索栏

Kibana的搜索核心是Lucene风格的结构化查询,结合Angular Material组件可以快速搭建出功能匹配的搜索栏,以下是完整实现方案:

核心功能对齐

先明确要覆盖的Kibana搜索核心特性:

  • 字段+运算符+值的结构化查询(如 level: ERROR、timestamp > "2024-01-01")
  • 实时语法解析与错误标记
  • 字段/运算符/常用值的下拉联想
  • 布尔逻辑(AND/OR/NOT)支持
  • 查询模板的保存与加载

基于Angular Material的代码示例

1. 组件模板(search-bar.component.html)

用Material的输入框、芯片、自动完成组件搭建UI:

<mat-card class="search-bar-wrapper">
  <mat-form-field appearance="outline" class="w-full">
    <mat-label>日志高级搜索</mat-label>
    <input 
      matInput 
      [(ngModel)]="searchQuery" 
      (input)="parseQuery()" 
      (keydown.enter)="executeSearch()"
      [matAutocomplete]="auto"
    >
    <mat-chip-list #chipList>
      <mat-chip 
        *ngFor="let token of parsedTokens" 
        [color]="token.valid ? 'primary' : 'warn'" 
        [removable]="true" 
        (removed)="removeToken(token)"
      >
        {{ token.display }}
        <mat-icon matChipRemove>cancel</mat-icon>
      </mat-chip>
    </mat-chip-list>
  </mat-form-field>

  <!-- 联想下拉面板 -->
  <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayOption">
    <mat-option 
      *ngFor="let option of autocompleteOptions" 
      (onSelectionChange)="insertOption(option)"
    >
      {{ option.label }}
    </mat-option>
  </mat-autocomplete>

  <!-- 操作按钮组 -->
  <div class="search-actions">
    <button mat-raised-button color="primary" (click)="executeSearch()">执行搜索</button>
    <button mat-raised-button (click)="saveQuery()">保存查询</button>
    <button mat-raised-button (click)="loadSavedQueries()">加载历史</button>
  </div>
</mat-card>

2. 组件逻辑(search-bar.component.ts)

实现语法解析、联想、查询执行核心功能:

import { Component, ViewChild } from '@angular/core';
import { MatAutocomplete } from '@angular/material/autocomplete';
import { MatChipList } from '@angular/material/chips';

interface ParsedToken {
  display: string;
  field?: string;
  operator?: string;
  value?: string;
  valid: boolean;
}

interface AutocompleteOption {
  label: string;
  type: 'field' | 'operator' | 'value';
  value: string;
}

@Component({
  selector: 'app-log-search-bar',
  templateUrl: './search-bar.component.html',
  styleUrls: ['./search-bar.component.scss']
})
export class LogSearchBarComponent {
  searchQuery = '';
  parsedTokens: ParsedToken[] = [];
  autocompleteOptions: AutocompleteOption[] = [];

  @ViewChild('auto') autocomplete!: MatAutocomplete;
  @ViewChild('chipList') chipList!: MatChipList;

  // 预定义日志元数据(可从后端动态拉取)
  private availableFields = ['level', 'timestamp', 'service', 'message', 'user_id'];
  private availableOperators = ['=', '>', '<', '>=', '<=', ':', '~'];
  private recentValues = ['ERROR', 'INFO', '2024-01-01', 'auth-service'];

  parseQuery() {
    this.parsedTokens = [];
    // 按空格拆分,忽略引号内的空格
    const parts = this.searchQuery.split(/\s+(?=(?:[^"]*"[^"]*")*[^"]*$)/);
    
    parts.forEach(part => {
      // 匹配字段+运算符格式
      const fieldOpMatch = part.match(/^([\w_]+)([:=><~]+)/);
      if (fieldOpMatch) {
        this.parsedTokens.push({
          display: part,
          field: fieldOpMatch[1],
          operator: fieldOpMatch[2],
          value: part.slice(fieldOpMatch[0].length),
          valid: this.availableFields.includes(fieldOpMatch[1]) && this.availableOperators.includes(fieldOpMatch[2])
        });
      } 
      // 匹配布尔关键字
      else if (['AND', 'OR', 'NOT'].includes(part.toUpperCase())) {
        this.parsedTokens.push({
          display: part.toUpperCase(),
          valid: true
        });
      } 
      // 普通字符串或无效语法
      else {
        this.parsedTokens.push({
          display: part,
          valid: false
        });
      }
    });

    this.updateAutocomplete();
  }

  updateAutocomplete() {
    const lastSegment = this.searchQuery.split(/\s+/).pop() || '';
    this.autocompleteOptions = [];

    // 联想字段
    this.availableFields.forEach(field => {
      if (field.startsWith(lastSegment.toLowerCase())) {
        this.autocompleteOptions.push({ label: `${field}:`, type: 'field', value: `${field}: ` });
      }
    });

    // 联想运算符(当输入的是字段名时)
    if (lastSegment.match(/^[\w_]+$/)) {
      this.availableOperators.forEach(op => {
        this.autocompleteOptions.push({ label: op, type: 'operator', value: op });
      });
    }

    // 联想常用值
    this.recentValues.forEach(val => {
      if (val.toLowerCase().includes(lastSegment.toLowerCase())) {
        this.autocompleteOptions.push({ label: val, type: 'value', value: val });
      }
    });
  }

  insertOption(option: AutocompleteOption) {
    const segments = this.searchQuery.split(/\s+/);
    segments.pop();
    this.searchQuery = [...segments, option.value].join(' ').trim() + ' ';
    this.parseQuery();
  }

  removeToken(token: ParsedToken) {
    this.searchQuery = this.searchQuery.replace(new RegExp(`\\s*${token.display}\\s*`), ' ').trim();
    this.parseQuery();
  }

  executeSearch() {
    // 将解析后的token转换为后端API可识别的参数
    const queryParams = this.parsedTokens
      .filter(t => t.field)
      .map(t => ({
        field: t.field,
        operator: t.operator,
        value: t.value?.replace(/^"|"$/g, '') // 移除引号
      }));
    
    console.log('执行搜索参数:', queryParams);
    // 对接日志API:this.logService.search(queryParams).subscribe(...)
  }

  saveQuery() {
    const savedQueries = JSON.parse(localStorage.getItem('saved_log_queries') || '[]');
    savedQueries.push({
      name: `查询${savedQueries.length + 1}`,
      query: this.searchQuery,
      createdAt: new Date().toISOString()
    });
    localStorage.setItem('saved_log_queries', JSON.stringify(savedQueries));
  }

  loadSavedQueries() {
    const savedQueries = JSON.parse(localStorage.getItem('saved_log_queries') || '[]');
    console.log('已保存查询:', savedQueries);
    // 可扩展为弹出选择框供用户选择加载
  }

  displayOption(option: AutocompleteOption) {
    return option.label;
  }
}

3. 样式调整(search-bar.component.scss)

贴近Kibana的简洁风格:

.search-bar-wrapper {
  padding: 16px;
  max-width: 900px;
  margin: 24px auto;
}

.w-full {
  width: 100%;
}

.search-actions {
  margin-top: 16px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

mat-chip {
  margin: 4px;
  font-size: 0.9rem;
}

扩展优化方向

  • 集成Monaco Editor替换原生输入框,实现专业的Lucene语法高亮
  • 对接后端日志元数据接口,动态获取可用字段和热门值
  • 添加查询语法实时校验,在输入框内标记错误位置
  • 实现时间范围快速选择器(如「最近1小时」「今天」)
  • 支持查询模板的导入导出

内容的提问来源于stack exchange,提问作者Saurabh Sharma 20bcs074

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:16:08