SPL学習備忘録 基本構造マスターしよう!まとめ編 Splunk(※クラシック)でオリジナルチャートを作るスキルを一緒に追加しよう
SPL(Search Processing Language)基礎解説:新人エンジニア向け
🎯 SPLとは?
Search Processing Language = Splunkでデータを検索・加工・集計するための専用言語
イメージ:
- SQL = データベース用の言語
- SPL = Splunk用の言語
📌 SPLの基本構造
パイプライン方式
データ取得 | 絞り込み | 加工 | 集計 | 表示
特徴: |(パイプ)で処理を繋げていく
🔍 今回使った SPL を完全解説
完全版コード
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
1️⃣ index=20260115tra
📖 意味
データソース(index)を指定
🔧 構文
index=<インデックス名>
💡 新人向け解説
- Splunkに取り込んだデータの「置き場所」
- CSVをアップロードすると自動で index が作られる
- 複数のindexがある場合、ここで絞り込む
📊 例
index=web_logs # Webサーバーのログ
index=app_errors # アプリケーションエラー
index=20260115tra # 今回の道路データ
2️⃣ | where like(location,"%湾岸%")
📖 意味
location列に「湾岸」という文字を含むデータだけ抽出
🔧 構文
| where like(<フィールド名>, "<パターン>")
💡 新人向け解説
-
%はワイルドカード(何でもマッチ) - SQL の
LIKEと同じ -
%湾岸%= 「前後に何があっても湾岸を含めばOK」
📊 マッチ例
| location | マッチするか? |
|---|---|
| 首都高 湾岸線 東京 | ✅ マッチ |
| 湾岸道路 | ✅ マッチ |
| 東京湾岸 | ✅ マッチ |
| 東名高速 | ❌ マッチしない |
🎯 応用パターン
# 前方一致
| where like(location, "首都高%")
# 後方一致
| where like(location, "%高速")
# 完全一致
| where location="首都高 湾岸線"
# 複数条件(OR)
| where like(location, "%湾岸%") OR like(location, "%東名%")
# 複数条件(AND)
| where like(location, "%首都高%") AND like(location, "%湾岸%")
3️⃣ | sort -_time
📖 意味
_time(時刻)フィールドで降順ソート(新しい順)
🔧 構文
| sort <-フィールド名> # 降順(大→小)
| sort <フィールド名> # 昇順(小→大)
💡 新人向け解説
-
-がつくと降順 -
-がないと昇順 -
_timeは Splunk の特殊フィールド(全データに自動付与)
📊 ソート結果イメージ
元データ:
_time: 2024-01-15 18:30 result: OK
_time: 2024-01-15 18:40 result: NG
_time: 2024-01-15 18:35 result: OK
sort -_time 後:
_time: 2024-01-15 18:40 result: NG ← 最新
_time: 2024-01-15 18:35 result: OK
_time: 2024-01-15 18:30 result: OK
🎯 応用パターン
# 昇順(古い順)
| sort _time
# 複数フィールドでソート
| sort -_time, location
# 数値フィールドでソート
| sort -count
# ソート後の件数制限
| sort -_time | head 10
4️⃣ | head 1
📖 意味
上から1件だけ取得(最新1件)
🔧 構文
| head <件数>
💡 新人向け解説
- 最重要コマンド!
-
sort -_timeと組み合わせて「最新1件」を取得 - これがないと過去の全データが対象になる
📊 動作イメージ
head 1 なし:
2024-01-15 18:40 NG ← 最新
2024-01-15 18:35 OK
2024-01-15 18:30 NG
2024-01-15 18:25 OK
↓
全部が処理対象(NGが2件ある)
head 1 あり:
2024-01-15 18:40 NG ← これだけ
↓
最新の1件だけ処理(NGが1件)
🎯 応用パターン
# 最新5件
| head 5
# 最古の1件(昇順ソート + head)
| sort _time | head 1
# 最新10件の次の10件(offset)
| sort -_time | head 20 | tail 10
5️⃣ | eval is_ng=if(result="NG",1,0)
📖 意味
新しいフィールド is_ng を作成し、条件分岐で値を設定
🔧 構文
| eval <新フィールド名>=if(<条件>, <真の値>, <偽の値>)
💡 新人向け解説
-
eval= 計算・加工用のコマンド -
if()= 3項演算子(プログラミングの? :と同じ) - 結果を数値化することでトークン判定が簡単になる
📊 動作イメージ
入力データ:
result: NG
処理:
if(result="NG", 1, 0)
↓
result が "NG" か? → YES
↓
1 を返す
出力データ:
result: NG
is_ng: 1
🎯 応用パターン
複数条件(case)
| eval status=case(
result="NG", "赤",
result="WARN", "黄",
result="OK", "緑",
1=1, "不明"
)
計算
| eval total=price * quantity
| eval discount_price=price * 0.9
文字列操作
| eval full_name=first_name." ".last_name
| eval upper_location=upper(location)
日時操作
| eval hour=strftime(_time, "%H")
| eval date=strftime(_time, "%Y-%m-%d")
NULL チェック
| eval is_empty=if(isnull(field), "空", "有")
🎓 SPL の処理順序(重要)
今回のクエリを分解
index=20260115tra # ① データ取得
| where like(location,"%湾岸%") # ② 絞り込み
| sort -_time # ③ ソート
| head 1 # ④ 最新1件
| eval is_ng=if(result="NG",1,0) # ⑤ 加工
📊 各段階でのデータ変化
① index=20260115tra
┌─────────────────────────────┐
│ 100万件のデータ │
└─────────────────────────────┘
② | where like(location,"%湾岸%")
┌─────────────────────────────┐
│ 1,000件(湾岸関連のみ) │
└─────────────────────────────┘
③ | sort -_time
┌─────────────────────────────┐
│ 1,000件(新しい順に並び替え)│
└─────────────────────────────┘
④ | head 1
┌─────────────────────────────┐
│ 1件(最新のみ) │
└─────────────────────────────┘
⑤ | eval is_ng=if(result="NG",1,0)
┌─────────────────────────────┐
│ 1件(is_ng フィールド追加) │
│ location: 首都高湾岸線 │
│ result: NG │
│ is_ng: 1 │
└─────────────────────────────┘
🔧 よく使う SPL コマンド一覧
データ取得系
| コマンド | 意味 | 例 |
|---|---|---|
index= |
データソース指定 | index=web_logs |
source= |
ファイル指定 | source="/var/log/app.log" |
sourcetype= |
データ形式指定 | sourcetype=access_combined |
絞り込み系
| コマンド | 意味 | 例 |
|---|---|---|
where |
条件絞り込み | where status=200 |
search |
キーワード検索 | search error |
like |
パターンマッチ | like(field, "%keyword%") |
in |
リスト照合 | where status in(200,201,204) |
並び替え・抽出系
| コマンド | 意味 | 例 |
|---|---|---|
sort |
ソート | sort -_time |
head |
上からN件 | head 10 |
tail |
下からN件 | tail 10 |
dedup |
重複削除 | dedup user_id |
加工系
| コマンド | 意味 | 例 |
|---|---|---|
eval |
フィールド作成・計算 | eval total=price*qty |
rex |
正規表現で抽出 | rex field=_raw "(?<ip>\d+\.\d+\.\d+\.\d+)" |
replace |
文字列置換 | eval status=replace(status,"OK","正常") |
集計系
| コマンド | 意味 | 例 |
|---|---|---|
stats |
集計 | stats count by status |
count |
件数 | stats count as total |
sum |
合計 | stats sum(amount) by user |
avg |
平均 | stats avg(response_time) |
max / min
|
最大/最小 | stats max(price) |
時系列系
| コマンド | 意味 | 例 |
|---|---|---|
timechart |
時系列グラフ | timechart count by status |
bin |
時間バケット | bin _time span=1h |
earliest / latest
|
時刻範囲 | earliest=-24h |
🎯 実践パターン集
パターン1:最新のステータスを取得
index=mydata
| where user_id=12345
| sort -_time
| head 1
| table user_id, status, _time
パターン2:エラー件数を集計
index=app_logs
| where level="ERROR"
| stats count by error_type
| sort -count
パターン3:時間帯別の平均応答時間
index=web_logs
| eval hour=strftime(_time, "%H")
| stats avg(response_time) by hour
| sort hour
パターン4:上位10ユーザー
index=access_logs
| stats count by user_id
| sort -count
| head 10
パターン5:条件付き集計
index=sales
| stats
count as total,
count(eval(status="completed")) as success,
count(eval(status="failed")) as failed
by store_id
⚠️ よくあるミス
ミス1:head の位置
❌ NG
index=mydata
| head 1
| sort -_time
↓
ランダムな1件をソートしても意味ない
✅ OK
index=mydata
| sort -_time
| head 1
↓
ソートしてから最新1件
ミス2:大文字小文字
❌ NG
| where result="ng"
↓
データが "NG" なのでマッチしない
✅ OK
| where result="NG"
または
| where lower(result)="ng"
ミス3:where と search の混同
# where:フィールド名を指定
| where status=200
# search:全文検索
| search "status=200"
🚀 デバッグテクニック
テクニック1:途中結果を確認
index=20260115tra
| where like(location,"%湾岸%")
| table location, _time, result # ← 途中で確認
テクニック2:件数確認
index=20260115tra
| where like(location,"%湾岸%")
| stats count # ← 何件あるか確認
テクニック3:フィールド一覧
index=20260115tra
| head 1
| table * # ← 全フィールドを表示
📚 学習ロードマップ
レベル1(初級)
- ✅
index,where,sort,head - ✅
evalの基本(if, 四則演算) - ✅
stats count
レベル2(中級)
- 🔶
statsの高度な使い方(sum, avg, max, min) - 🔶
timechart - 🔶
rex(正規表現)
レベル3(上級)
- 🔥
join(結合) - 🔥
eventstats,streamstats - 🔥
lookup(外部データ参照)
📝 まとめ
| コマンド | 役割 | 重要度 |
|---|---|---|
index= |
データ取得 | ⭐⭐⭐ |
where |
絞り込み | ⭐⭐⭐ |
sort |
並び替え | ⭐⭐⭐ |
head |
件数制限 | ⭐⭐⭐ |
eval |
加工 | ⭐⭐⭐ |
stats |
集計 | ⭐⭐ |
table |
表示 | ⭐⭐ |
SPL は書いて覚える! まずは今回のクエリを理解して、1行ずつ変えて実験してみよう 🚀
Splunk SPL 文法作成のコツ:実践的テクニック集
🎯 この記事で学べること
- SPL文法の 組み立て方の思考プロセス
- 新人が間違えやすいポイントと回避法
- 実務で使える デバッグ・検証手順
📌 SPL文法作成の基本思考フロー
✅ 5ステップ思考法
1️⃣ ゴールを明確化する
↓
2️⃣ データの形を確認する
↓
3️⃣ 処理順序を決める
↓
4️⃣ 1行ずつ組み立てる
↓
5️⃣ 検証・デバッグする
1️⃣ ゴールを明確化する
🎯 最初に決めるべき3つのこと
| 項目 | 質問 | 例 |
|---|---|---|
| 何を取得? | どのデータが欲しい? | 首都高速 湾岸線の最新ステータス |
| どう判定? | 何を基準に判断? | result列が"NG"なら赤 |
| どう表示? | 出力形式は? | 緑/赤のボックス表示 |
📝 ゴール設定の例
悪い例(曖昧):
「道路の状態を見たい」
良い例(明確):
「首都高速 湾岸線の最新(_time最大)の1件について、
result列が"NG"なら赤、それ以外なら緑にする」
2️⃣ データの形を確認する
🔍 必ず最初にやること
index=20260115tra
| head 10
| table *
確認ポイント:
- ✅ フィールド名は何?(location? road? status?)
- ✅ 値のフォーマットは?("NG" or "ng"? 全角/半角?)
- ✅ 時刻フィールドは?(_time? timestamp?)
📊 実際のデータ例
location _time result
首都高速 湾岸線 東京 2024-01-18 09:00:00 正常
国道16号 相模原市 2024-01-18 08:50:00 NG
東名高速 横浜 2024-01-18 08:45:00 正常
ここから分かること:
- location列に「首都高速 湾岸線」が含まれる
- result列は「NG」「正常」(日本語)
- _time が時刻フィールド
3️⃣ 処理順序を決める
🧠 SPLの黄金パターン
取得 → 絞り込み → 並び替え → 件数制限 → 加工 → 集計
📋 今回のケースで考える
やりたいこと:
「首都高速 湾岸線の最新1件のresultがNGなら赤」
処理順序:
1. index=20260115tra # 全データ取得
2. where like(location,...) # 湾岸線だけ絞る
3. sort -_time # 新しい順に並べる
4. head 1 # 最新1件だけ残す
5. eval is_ng=if(...) # NG判定用フィールド追加
⚠️ よくある失敗:順序ミス
❌ 間違った順序
index=20260115tra
| head 1 # ← 先に1件取ると
| where like(location,...) # ← 湾岸線じゃない可能性
✅ 正しい順序
index=20260115tra
| where like(location,...) # ← 先に絞り込んでから
| head 1 # ← 1件取る
4️⃣ 1行ずつ組み立てる
🔨 段階的構築法
Step 1: データ取得だけ
index=20260115tra
実行して確認: 件数が多すぎないか?
Step 2: 絞り込み追加
index=20260115tra
| where like(location,"%首都高速 湾岸線%")
実行して確認: 湾岸線だけになったか?
Step 3: ソート追加
index=20260115tra
| where like(location,"%首都高速 湾岸線%")
| sort -_time
| table location, _time, result # ← 確認用
実行して確認: 最新が一番上に来たか?
Step 4: 最新1件に絞る
index=20260115tra
| where like(location,"%首都高速 湾岸線%")
| sort -_time
| head 1
| table location, _time, result
実行して確認: 1件だけになったか?
Step 5: 判定フィールド追加
index=20260115tra
| where like(location,"%首都高速 湾岸線%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
| table location, _time, result, is_ng # ← is_ngも表示
実行して確認: is_ngが正しく1/0になったか?
💡 この方法の利点
| 方法 | メリット |
|---|---|
| 一気に書く | ❌ エラー箇所が分からない |
| 1行ずつ | ✅ どこで間違えたか即分かる |
5️⃣ 検証・デバッグする
🔍 デバッグの3大テクニック
テクニック1:table で中身を見る
# 任意の場所に追加
| table * # 全フィールド表示
| table location, _time, result # 特定フィールドのみ
使い方:
index=20260115tra
| where like(location,"%湾岸%")
| table * # ← ここで一旦確認
| sort -_time
| head 1
テクニック2:stats count で件数確認
index=20260115tra
| where like(location,"%湾岸%")
| stats count # ← 何件ヒットしたか?
活用例:
# パターン1: 条件を変えて比較
| where like(location,"%湾岸%") | stats count # 100件
| where like(location,"%首都高%") | stats count # 50件
| where location="首都高速 湾岸線" | stats count # 10件
テクニック3:eval の結果を確認
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
| eval debug=result # ← 元の値も残す
| table location, result, debug, is_ng
🎯 実践的な文法作成パターン集
パターン1:条件分岐(if文)
基本形
| eval 新フィールド=if(条件, 真の値, 偽の値)
実例
# 単純な条件
| eval status=if(result="NG", "赤", "緑")
# 数値比較
| eval level=if(score>=80, "合格", "不合格")
# フィールドの存在確認
| eval has_data=if(isnull(field), 0, 1)
パターン2:複数条件分岐(case文)
基本形
| eval 新フィールド=case(
条件1, 値1,
条件2, 値2,
条件3, 値3,
1=1, デフォルト値
)
実例
# 3段階の色分け
| eval color=case(
result="NG", "red",
result="WARN", "yellow",
result="OK", "green",
1=1, "gray"
)
# スコアでランク分け
| eval rank=case(
score>=90, "S",
score>=80, "A",
score>=70, "B",
score>=60, "C",
1=1, "D"
)
パターン3:文字列操作
部分一致検索
# like 関数
| where like(location, "%湾岸%")
# 正規表現
| where match(location, "湾岸|東名|中央")
文字列結合
# ドット演算子
| eval full_name=first_name." ".last_name
# 複数フィールド
| eval message=location." は ".result." です"
大文字小文字変換
| eval lower_result=lower(result) # 小文字化
| eval upper_result=upper(result) # 大文字化
パターン4:日時操作
時刻フォーマット変換
# 時間だけ抽出
| eval hour=strftime(_time, "%H")
# 日付だけ抽出
| eval date=strftime(_time, "%Y-%m-%d")
# 曜日抽出
| eval weekday=strftime(_time, "%A")
時間計算
# 1時間前
| eval one_hour_ago=_time - 3600
# 現在時刻との差
| eval elapsed=now() - _time
パターン5:集計系
グループ集計
# 道路ごとの件数
| stats count by location
# 道路ごとのNG件数
| stats count(eval(result="NG")) as ng_count by location
# 複数集計
| stats
count as total,
count(eval(result="NG")) as ng,
count(eval(result="OK")) as ok
by location
最大・最小・平均
# 最新の_time
| stats latest(_time) as latest_time by location
# 最大値
| stats max(score) as max_score by user
# 平均値
| stats avg(response_time) as avg_time by server
🚨 よくあるミスと対策
ミス1:大文字小文字の不一致
❌ 間違い
| where result="ng" # データは "NG"
✅ 対策1: 正確に書く
| where result="NG"
✅ 対策2: 小文字化して比較
| where lower(result)="ng"
ミス2:全角・半角の混在
❌ 間違い
| where like(location, "%湾岸%") # データは全角スペース
✅ 対策: 実データを確認
| table location # ← まず確認
| where like(location, "%首都高速 湾岸線%") # 全角スペース
ミス3:フィールド名の間違い
❌ 間違い
| where status="NG" # フィールド名は result
✅ 対策: フィールド一覧を確認
| head 1 | table * # ← 全フィールド名を表示
ミス4:条件の優先順位
❌ 間違い
| where result="NG" OR result="WARN" AND location="湾岸"
# → (NG) OR (WARN かつ 湾岸) になる
✅ 正しい
| where (result="NG" OR result="WARN") AND location="湾岸"
# → (NG または WARN) かつ 湾岸
ミス5:ソート後の head 忘れ
❌ 間違い
| sort -_time
# → 全件がソートされただけ
✅ 正しい
| sort -_time
| head 1
# → 最新1件だけ取得
🧪 デバッグの実践例
ケース:「赤にならない」
Step 1: データ確認
index=20260115tra
| where like(location,"%湾岸%")
| table location, _time, result
確認すること:
- ✅ location に「湾岸」が含まれているか?
- ✅ result の値は?("NG"? "ng"? "エラー"?)
Step 2: 最新1件確認
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| table location, _time, result
確認すること:
- ✅ 最新の _time は合っているか?
- ✅ その時の result は何か?
Step 3: 判定ロジック確認
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
| table location, result, is_ng
確認すること:
- ✅ is_ng が 1 になるべきなのに 0 になっていないか?
- ✅ result の値が "NG" と完全一致しているか?
Step 4: 大文字小文字を疑う
| eval is_ng=if(lower(result)="ng",1,0)
🎓 SPL文法作成のコツ まとめ
✅ 黄金ルール
| ルール | 内容 |
|---|---|
| 1. 小さく始める | いきなり完成形を書かない |
| 2. 1行ずつ確認 |
table や stats count で検証 |
| 3. データを見る | 想像で書かず実データを確認 |
| 4. 順序を守る | 取得→絞込→ソート→制限→加工 |
| 5. コメントを書く |
# コメント で意図を残す |
📋 チェックリスト
□ index名は合っているか?
□ フィールド名は正確か?
□ 大文字小文字は合っているか?
□ 全角半角は合っているか?
□ ソートしてから head しているか?
□ 条件の優先順位は正しいか?
□ 中間結果を確認したか?
🚀 次のステップ
初級 → 中級への道
# 初級: 単純な絞り込み
| where result="NG"
# 中級: 複数条件の組み合わせ
| where result="NG" AND priority="高"
# 上級: 正規表現と集計
| rex field=message "(?<error_code>\d{3})"
| stats count by error_code
実務でよく使うパターン
- エラー率の計算
| stats
count as total,
count(eval(result="NG")) as errors
| eval error_rate=round(errors/total*100, 2)
- 時系列グラフ
| timechart span=1h count by result
- トップN抽出
| stats count by location
| sort -count
| head 10
最後に: SPLは「書いて→実行→確認」の繰り返しで上達します。失敗を恐れず、どんどん試してください 💪
🎫 Splunkトークン完全ガイド|動的ダッシュボードの心臓部
トークンを徹底解説します!ダッシュボードを動的にする魔法の仕組みです!
📋 目次
Step1: トークンとは?(うさうさラーメン店で理解)
Step2: トークンの種類と基本文法
Step3: トークンの設定方法(set/unset/condition)
Step4: 実践例1: フォーム入力による絞り込み
Step5: 実践例2: ドリルダウンで詳細表示
Step6: 実践例3: 複数トークンの連携
Step7: デバッグ方法とトラブルシューティング
🍜 Step1: トークンとは?(うさうさラーメン店で理解)
トークンの概念
┌─────────────────────────────────────────┐
│ うさうさラーメン店で考えよう! │
│ │
│ お客さん「味噌ラーメン大盛りで!」 │
│ ↓ │
│ 注文票に記録「味=$味噌$, 量=$大盛り$」 │
│ ↓ │
│ キッチン「$味$で$量$を作ります」 │
│ ↓ │
│ 結果「味噌ラーメン大盛りができた!」 │
│ │
│ トークン = 注文票に書かれた変数 │
└─────────────────────────────────────────┘
Splunkでのトークン
┌─────────────────────────────────────────┐
│ ユーザー「Server-Aのデータを見たい!」 │
│ ↓ │
│ トークンに保存「server_name=$Server-A$」│
│ ↓ │
│ サーチクエリ │
│ index=monitoring device=$server_name$ │
│ ↓ │
│ 実行されるクエリ │
│ index=monitoring device=Server-A │
└─────────────────────────────────────────┘
トークンの役割
| 役割 | 説明 | 例 |
|---|---|---|
| 動的フィルタリング | ユーザー入力で検索条件を変更 | デバイス名選択 |
| 値の受け渡し | パネル間でデータを連携 | クリックで詳細表示 |
| 条件分岐 | 状況に応じて表示を切り替え | エラー時に警告表示 |
| 再利用性 | 同じ値を複数箇所で使用 | 時間範囲の統一 |
🏷️ Step2: トークンの種類と基本文法
トークンの種類
┌─────────────────────────────────────────────┐
│ 1. デフォルトトークン(Splunk標準) │
│ ├─ $earliest$ (検索開始時刻) │
│ ├─ $latest$ (検索終了時刻) │
│ └─ $env:page$ (現在のページ名) │
│ │
│ 2. カスタムトークン(ユーザー定義) │
│ ├─ $my_token$ (自由に命名) │
│ ├─ $server$ (サーバー名) │
│ └─ $status$ (ステータス) │
│ │
│ 3. フォームトークン(入力フォーム) │
│ ├─ $form.field1$ (入力1) │
│ └─ $form.field2$ (入力2) │
└─────────────────────────────────────────────┘
トークンの基本文法
<!-- トークンの参照 -->
$token_name$
<!-- ↑ ドル記号で囲んで使用 -->
<!-- フォームトークンの参照 -->
$form.input_name$
<!-- ↑ form.プレフィックスをつける -->
<!-- デフォルト値付きトークン -->
$token_name|default_value$
<!-- ↑ トークンが未定義の場合はdefault_valueを使用 -->
⚙️ Step3: トークンの設定方法
方法1: <set> タグで設定
<!-- 基本的なset構文 -->
<set token="token_name">値</set>
<!-- ↑ トークンに固定値を設定 -->
<!-- サーチ結果からsetする -->
<search>
<query>
index=monitoring
| stats count by device_name
| head 1
| table device_name
</query>
<!-- サーチ完了時にトークンを設定 -->
<done>
<set token="first_device">$result.device_name$</set>
<!-- ↑ 検索結果の最初のdevice_nameをトークンに格納 -->
</done>
</search>
方法2: <unset> タグで削除
<!-- トークンを削除 -->
<unset token="token_name"></unset>
<!-- ↑ トークンを未定義状態にする -->
<!-- 条件付きで削除 -->
<change>
<condition value="reset">
<!-- "reset"が選択されたらトークンを削除 -->
<unset token="server_filter"></unset>
</condition>
</change>
方法3: <condition> タグで条件分岐
<!-- 条件分岐でトークンを設定 -->
<change>
<!-- フォームの値が変更されたとき -->
<condition label="OK">
<!-- "OK"が選択された場合 -->
<set token="status_filter">status="OK"</set>
<!-- ↑ ステータスフィルタを設定 -->
</condition>
<condition label="NG">
<!-- "NG"が選択された場合 -->
<set token="status_filter">status="NG"</set>
</condition>
<condition label="全て">
<!-- "全て"が選択された場合 -->
<unset token="status_filter"></unset>
<!-- ↑ フィルタを解除 -->
</condition>
</change>
📝 Step4: 実践例1 - フォーム入力による絞り込み
完全なダッシュボード例
<form>
<!-- formタグを使用(ダッシュボードをフォーム型に) -->
<label>🔍 サーバー監視ダッシュボード</label>
<!-- フィールドセット(入力フォームのコンテナ) -->
<fieldset submitButton="true" autoRun="false">
<!-- submitButton: 検索ボタンを表示 -->
<!-- autoRun: 自動実行しない(ボタンクリックで実行) -->
<!-- 入力1: ドロップダウンでサーバー選択 -->
<input type="dropdown" token="selected_server">
<!-- type: 入力タイプを指定 -->
<!-- token: このフォームの値を格納するトークン名 -->
<label>サーバー選択</label>
<!-- フォームのラベル表示 -->
<choice value="*">全サーバー</choice>
<!-- デフォルト選択肢(ワイルドカード) -->
<choice value="Server-A">Server-A</choice>
<!-- 選択肢1 -->
<choice value="Server-B">Server-B</choice>
<!-- 選択肢2 -->
<choice value="Server-C">Server-C</choice>
<!-- 選択肢3 -->
<default>*</default>
<!-- 初期選択値 -->
<initialValue>*</initialValue>
<!-- ページロード時の初期値 -->
</input>
<!-- 入力2: ラジオボタンでステータス選択 -->
<input type="radio" token="selected_status">
<!-- ラジオボタン形式の入力 -->
<label>ステータス</label>
<choice value="*">全て</choice>
<!-- ワイルドカードで全て表示 -->
<choice value="OK">OK</choice>
<choice value="WARNING">WARNING</choice>
<choice value="NG">NG</choice>
<default>*</default>
</input>
<!-- 入力3: テキストボックスで温度閾値 -->
<input type="text" token="temp_threshold">
<!-- テキスト入力フォーム -->
<label>温度閾値(℃)</label>
<default>70</default>
<!-- デフォルト値を70に設定 -->
</input>
<!-- 入力4: 時間範囲ピッカー -->
<input type="time" token="time_range">
<!-- 時間範囲選択フォーム -->
<label>時間範囲</label>
<default>
<earliest>-24h@h</earliest>
<!-- 24時間前から -->
<latest>now</latest>
<!-- 現在まで -->
</default>
</input>
</fieldset>
<!-- 検索結果を表示するパネル -->
<row>
<panel>
<title>📊 フィルタリング結果</title>
<table>
<search>
<query>
index=monitoring sourcetype=csv
<!-- インデックスからデータを検索 -->
| search device_name=$selected_server$
<!-- トークンを使ってサーバーでフィルタ -->
<!-- $selected_server$ が "*" なら全て -->
<!-- "Server-A" なら Server-A のみ -->
| search status=$selected_status$
<!-- トークンを使ってステータスでフィルタ -->
| where temperature > $temp_threshold$
<!-- 温度が閾値を超えるものだけ表示 -->
| table _time device_name status temperature response_time
<!-- 表示するフィールドを選択 -->
| sort -_time
<!-- 時刻の降順でソート -->
</query>
<!-- トークンから時間範囲を設定 -->
<earliest>$time_range.earliest$</earliest>
<latest>$time_range.latest$</latest>
</search>
<!-- テーブルのオプション -->
<option name="drilldown">none</option>
<option name="count">20</option>
</table>
</panel>
</row>
<!-- トークンの値を表示(デバッグ用) -->
<row>
<panel>
<title>🐛 現在のトークン値(デバッグ用)</title>
<html>
<div style="padding: 15px; background: #f5f5f5; border-radius: 8px; font-family: monospace;">
<!-- トークンの値を表示 -->
<p><strong>selected_server:</strong> $selected_server$</p>
<p><strong>selected_status:</strong> $selected_status$</p>
<p><strong>temp_threshold:</strong> $temp_threshold$</p>
<p><strong>earliest:</strong> $time_range.earliest$</p>
<p><strong>latest:</strong> $time_range.latest$</p>
</div>
</html>
</panel>
</row>
</form>
フォーム入力の動作フロー
┌─────────────────────────────────────┐
│ ユーザーがフォーム入力 │
│ ├─ サーバー: Server-A │
│ ├─ ステータス: NG │
│ └─ 温度閾値: 70 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ トークンに値が設定される │
│ ├─ $selected_server$ = "Server-A" │
│ ├─ $selected_status$ = "NG" │
│ └─ $temp_threshold$ = "70" │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ サーチクエリが展開される │
│ index=monitoring │
│ | search device_name=Server-A │
│ | search status=NG │
│ | where temperature > 70 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 結果が表示される │
│ Server-AでNGのデータのみ表示 │
└─────────────────────────────────────┘
🎯 Step5: 実践例2 - ドリルダウンで詳細表示
ドリルダウンとは?
┌─────────────────────────────────────┐
│ 一覧表示(クリック可能) │
│ ├─ Server-A (OK) ← クリック! │
│ ├─ Server-B (NG) │
│ └─ Server-C (OK) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 詳細パネルが表示される │
│ Server-Aの詳細情報: │
│ ├─ CPU使用率: 45% │
│ ├─ メモリ: 8GB/16GB │
│ └─ ディスク: 120GB/500GB │
└─────────────────────────────────────┘
完全実装例
<dashboard>
<label>🔍 ドリルダウンダッシュボード</label>
<!-- 初期状態では詳細パネルを非表示 -->
<init>
<!-- ダッシュボード初期化時の処理 -->
<set token="show_detail">false</set>
<!-- ↑ 詳細表示フラグを false に設定 -->
</init>
<!-- サーバー一覧パネル -->
<row>
<panel>
<title>📋 サーバー一覧(クリックで詳細表示)</title>
<table>
<search>
<query>
index=monitoring sourcetype=csv
| stats latest(status) as status,
latest(temperature) as temperature,
latest(response_time) as response_time
by device_name
<!-- デバイスごとに最新のステータス、温度、応答時間を集計 -->
| sort device_name
<!-- デバイス名でソート -->
</query>
<earliest>-24h@h</earliest>
<latest>now</latest>
</search>
<!-- ドリルダウンを有効化 -->
<option name="drilldown">cell</option>
<!-- ↑ セルクリックでドリルダウン -->
<!-- ドリルダウン時の動作定義 -->
<drilldown>
<!-- クリックされた行のdevice_nameを取得 -->
<set token="selected_device">$row.device_name$</set>
<!-- ↑ クリックした行のデバイス名をトークンに格納 -->
<!-- クリックされた行のstatusを取得 -->
<set token="selected_device_status">$row.status$</set>
<!-- 詳細パネルを表示 -->
<set token="show_detail">true</set>
<!-- ↑ フラグをtrueにして詳細パネルを表示 -->
</drilldown>
</table>
</panel>
</row>
<!-- 詳細情報パネル(条件付き表示) -->
<row depends="$show_detail$">
<!-- depends属性: トークンが定義されている場合のみ表示 -->
<panel>
<title>📊 $selected_device$ の詳細情報</title>
<!-- トークンをタイトルに埋め込み -->
<html>
<div style="padding: 10px; background: #e3f2fd; border-radius: 8px; margin-bottom: 15px;">
<!-- 現在選択されているデバイス情報を表示 -->
<h3 style="margin: 0; color: #1976d2;">
選択中: $selected_device$ (ステータス: $selected_device_status$)
</h3>
<!-- 閉じるボタン -->
<button style="margin-top: 10px; padding: 5px 15px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;"
onclick="
<!-- JavaScriptでトークンを削除 -->
require(['splunkjs/mvc/simplexml/ready!'], function(){
var tokens = require('splunkjs/mvc').Components.getInstance('default');
tokens.unset('show_detail');
});
">
✕ 閉じる
</button>
</div>
</html>
<!-- 時系列グラフ -->
<chart>
<title>温度推移</title>
<search>
<query>
index=monitoring sourcetype=csv device_name=$selected_device$
<!-- 選択されたデバイスのみに絞り込み -->
| timechart span=1h avg(temperature) as "平均温度"
<!-- 1時間ごとの平均温度を時系列で表示 -->
</query>
<earliest>-7d@d</earliest>
<latest>now</latest>
</search>
<option name="charting.chart">line</option>
<!-- 折れ線グラフで表示 -->
<option name="charting.axisTitleX.text">時刻</option>
<option name="charting.axisTitleY.text">温度(℃)</option>
</chart>
</panel>
<!-- 詳細テーブル -->
<panel>
<title>📋 詳細ログ</title>
<table>
<search>
<query>
index=monitoring sourcetype=csv device_name=$selected_device$
<!-- 選択されたデバイスのログを表示 -->
| table _time status temperature response_time location
<!-- 必要なフィールドを選択 -->
| sort -_time
<!-- 時刻の降順でソート -->
| head 50
<!-- 最新50件を表示 -->
</query>
<earliest>-24h@h</earliest>
<latest>now</latest>
</search>
<option name="count">10</option>
</table>
</panel>
</row>
</dashboard>
ドリルダウンの動作フロー
┌──────────────────────────────────────┐
│ 1. ユーザーがServer-Aをクリック │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 2. <drilldown> タグが発火 │
│ <set token="selected_device"> │
│ $row.device_name$ │
│ </set> │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 3. トークンに値が設定される │
│ $selected_device$ = "Server-A" │
│ $show_detail$ = "true" │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 4. depends属性で詳細パネルが表示 │
│ <row depends="$show_detail$"> │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ 5. Server-Aの詳細データが検索される │
│ device_name=Server-A で絞り込み │
└──────────────────────────────────────┘
🔗 Step6: 実践例3 - 複数トークンの連携
親子関係のあるトークン
<form>
<label>🔗 連鎖ドロップダウン</label>
<fieldset>
<!-- 親トークン: 場所を選択 -->
<input type="dropdown" token="selected_location" searchWhenChanged="true">
<!-- searchWhenChanged: 値変更時に自動検索 -->
<label>場所</label>
<choice value="*">全ての場所</choice>
<choice value="Tokyo">東京</choice>
<choice value="Osaka">大阪</choice>
<choice value="Nagoya">名古屋</choice>
<default>*</default>
<!-- 値が変更されたとき -->
<change>
<!-- 子トークンをリセット -->
<unset token="selected_server"></unset>
<!-- ↑ 場所を変更したらサーバー選択をリセット -->
</change>
</input>
<!-- 子トークン: サーバーを選択(場所に応じて変化) -->
<input type="dropdown" token="selected_server" depends="$selected_location$">
<!-- depends: 親トークンが設定されている場合のみ表示 -->
<label>サーバー</label>
<!-- 動的にサーバーリストを取得 -->
<search>
<query>
index=monitoring location=$selected_location$
<!-- 選択された場所のサーバーのみ検索 -->
| stats count by device_name
<!-- デバイス名でグループ化 -->
| table device_name
<!-- デバイス名のみ抽出 -->
| sort device_name
<!-- ソート -->
</query>
<earliest>-24h@h</earliest>
<latest>now</latest>
</search>
<!-- 検索結果からフィールドを取得 -->
<fieldForLabel>device_name</fieldForLabel>
<!-- ↑ 表示ラベルとして使用するフィールド -->
<fieldForValue>device_name</fieldForValue>
<!-- ↑ トークンに格納する値として使用するフィールド -->
<default>*</default>
</input>
<!-- 孫トークン: ステータスを選択 -->
<input type="radio" token="selected_status" depends="$selected_server$">
<!-- サーバーが選択されている場合のみ表示 -->
<label>ステータス</label>
<choice value="*">全て</choice>
<choice value="OK">OK</choice>
<choice value="WARNING">WARNING</choice>
<choice value="NG">NG</choice>
<default>*</default>
</input>
</fieldset>
<!-- 結果表示パネル -->
<row>
<panel>
<title>🎯 フィルタリング結果</title>
<table>
<search>
<query>
index=monitoring
| search location=$selected_location$
<!-- 場所でフィルタ -->
| search device_name=$selected_server$
<!-- サーバーでフィルタ -->
| search status=$selected_status$
<!-- ステータスでフィルタ -->
| table _time location device_name status temperature response_time
| sort -_time
</query>
<earliest>-24h@h</earliest>
<latest>now</latest>
</search>
</table>
</panel>
</row>
<!-- トークン連鎖の状態表示 -->
<row>
<panel>
<title>🔗 トークン連鎖の状態</title>
<html>
<div style="padding: 20px; background: #f5f5f5; border-radius: 8px; font-family: monospace;">
<div style="margin-bottom: 10px;">
<strong>1. 場所:</strong>
<span style="color: #1976d2; font-weight: bold;">$selected_location$</span>
<!-- ↑ 親トークン -->
</div>
<div style="margin-bottom: 10px; margin-left: 20px;">
<strong>└→ 2. サーバー:</strong>
<span style="color: #388e3c; font-weight: bold;">$selected_server$</span>
<!-- ↑ 子トークン(親に依存) -->
</div>
<div style="margin-left: 40px;">
<strong>└→ 3. ステータス:</strong>
<span style="color: #f57c00; font-weight: bold;">$selected_status$</span>
<!-- ↑ 孫トークン(子に依存) -->
</div>
</div>
</html>
</panel>
</row>
</form>
連鎖の動作フロー
┌────────────────────────────────────────┐
│ ユーザーが「東京」を選択 │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ 1. $selected_location$ = "Tokyo" │
│ 2. <change>タグが発火 │
│ └→ $selected_server$ をリセット │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ サーバードロップダウンが更新される │
│ location=Tokyo のサーバーのみ表示 │
│ ├─ Server-A │
│ └─ Server-C │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ ユーザーが「Server-A」を選択 │
│ $selected_server$ = "Server-A" │
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ ステータス選択が表示される │
│ (depends属性により) │
└────────────────────────────────────────┘
🐛 Step7: デバッグ方法とトラブルシューティング
デバッグパネルの実装
<dashboard>
<label>🐛 トークンデバッグダッシュボード</label>
<fieldset>
<input type="text" token="test_token">
<label>テストトークン</label>
<default>test_value</default>
</input>
</fieldset>
<!-- デバッグ専用パネル -->
<row>
<panel>
<title>🔍 トークンデバッグ情報</title>
<html>
<style>
/* デバッグ表示のスタイル */
.debug-container {
font-family: 'Courier New', monospace;
/* 等幅フォントを使用 */
background: #263238;
/* ダークな背景色 */
color: #aed581;
/* 緑色のテキスト */
padding: 20px;
/* 内側の余白 */
border-radius: 8px;
/* 角を丸める */
overflow-x: auto;
/* 横スクロール可能に */
}
.debug-row {
margin: 8px 0;
/* 各行の余白 */
padding: 5px;
/* 内側の余白 */
border-left: 3px solid #4caf50;
/* 左側に緑のボーダー */
padding-left: 10px;
/* 左側の余白 */
}
.token-name {
color: #81d4fa;
/* 水色でトークン名を表示 */
font-weight: bold;
/* 太字 */
}
.token-value {
color: #ffeb3b;
/* 黄色でトークン値を表示 */
}
.undefined {
color: #ef5350;
/* 赤色で未定義を表示 */
font-style: italic;
/* イタリック体 */
}
</style>
<div class="debug-container">
<h3 style="color: #fff; margin-top: 0;">📋 現在のトークン一覧</h3>
<!-- カスタムトークン -->
<div class="debug-row">
<span class="token-name">test_token:</span>
<span class="token-value">"$test_token$"</span>
<!-- トークンの値を表示 -->
</div>
<!-- デフォルトトークン -->
<div class="debug-row">
<span class="token-name">earliest:</span>
<span class="token-value">"$earliest$"</span>
</div>
<div class="debug-row">
<span class="token-name">latest:</span>
<span class="token-value">"$latest$"</span>
</div>
<!-- 未定義トークンのテスト -->
<div class="debug-row">
<span class="token-name">undefined_token:</span>
<span class="undefined">"$undefined_token$"</span>
<!-- 未定義の場合はそのまま表示される -->
</div>
<!-- デフォルト値付きトークン -->
<div class="debug-row">
<span class="token-name">with_default:</span>
<span class="token-value">"$undefined_token|デフォルト値$"</span>
<!-- 未定義の場合はデフォルト値が表示される -->
</div>
</div>
</html>
</panel>
</row>
<!-- JavaScriptでトークンを取得 -->
<row>
<panel>
<title>🔧 JavaScriptデバッグコンソール</title>
<html>
<div id="js-debug" style="background: #f5f5f5; padding: 15px; border-radius: 8px; font-family: monospace;"></div>
<script>
require([
'splunkjs/mvc',
// Splunk MVCフレームワークを読み込み
'splunkjs/mvc/simplexml/ready!'
// Simple XMLの準備完了を待つ
], function(mvc) {
// トークンモデルを取得
var tokens = mvc.Components.get('default');
// ↑ デフォルトトークンセットを取得
// トークンモデルを取得(サブミット済み)
var submittedTokens = mvc.Components.get('submitted');
// ↑ サブミット済みトークンセットを取得
// デバッグ情報を表示する関数
function updateDebug() {
var debugDiv = document.getElementById('js-debug');
// 表示用のdiv要素を取得
var html = '<h4>トークン詳細情報:</h4>';
// 見出しを追加
// 全トークンを取得
var allTokens = tokens.toJSON();
// ↑ トークンをJSONオブジェクトに変換
// トークンをループで表示
for (var key in allTokens) {
html += '<div><strong>' + key + ':</strong> ' + allTokens[key] + '</div>';
// 各トークンの名前と値を表示
}
// HTML更新
debugDiv.innerHTML = html;
// ↑ デバッグ情報を画面に表示
}
// 初期表示
updateDebug();
// トークン変更時に更新
tokens.on('change', function() {
updateDebug();
// ↑ トークンが変更されたら再表示
});
});
</script>
</html>
</panel>
</row>
</dashboard>
⚠️ よくあるトラブルと解決策
❌ 問題1: トークンが展開されない
表示結果: $token_name$
期待結果: actual_value
診断SPL:
| makeresults
| eval test_value="$token_name$"
<!-- トークンが展開されているか確認 -->
| table test_value
原因と解決策:
<!-- ❌ 間違い: ダブルクォートで囲んでいない -->
<set token="my_token">value</set>
<query>
index=main field=$my_token$
<!-- SPL内で裸のトークンは展開されない -->
</query>
<!-- ✅ 正しい: ダブルクォートで囲む -->
<set token="my_token">value</set>
<query>
index=main field="$my_token$"
<!-- ダブルクォートで囲むと正しく展開される -->
</query>
❌ 問題2: フォームトークンが更新されない
原因: searchWhenChanged が設定されていない
解決策:
<!-- ❌ 間違い -->
<input type="dropdown" token="my_token">
<label>選択</label>
<choice value="a">A</choice>
<!-- 値を変更しても検索が実行されない -->
</input>
<!-- ✅ 正しい -->
<input type="dropdown" token="my_token" searchWhenChanged="true">
<!-- searchWhenChanged属性を追加 -->
<label>選択</label>
<choice value="a">A</choice>
<!-- 値を変更すると自動的に検索が実行される -->
</input>
❌ 問題3: depends属性が動作しない
診断方法:
<!-- デバッグ用パネル -->
<row>
<panel>
<html>
<div>トークンの値: "$my_token$"</div>
<!-- トークンが設定されているか確認 -->
<div>長さ: $my_token|length$</div>
<!-- トークンの長さを確認 -->
</html>
</panel>
</row>
<!-- depends付きパネル -->
<row depends="$my_token$">
<!-- トークンが空文字列の場合も表示されない -->
<panel>
<title>詳細情報</title>
</panel>
</row>
解決策:
<!-- トークンが空文字列でも表示したい場合 -->
<set token="show_panel">true</set>
<!-- フラグ用のトークンを別途用意 -->
<row depends="$show_panel$">
<!-- フラグトークンで制御 -->
<panel>
<title>詳細情報</title>
</panel>
</row>
❌ 問題4: ドリルダウンが動作しない
チェックリスト:
<table>
<!-- 1. drilldownオプションを確認 -->
<option name="drilldown">cell</option>
<!-- ↑ "none"になっていないか確認 -->
<drilldown>
<!-- 2. トークン設定を確認 -->
<set token="clicked_value">$row.fieldname$</set>
<!-- ↑ フィールド名が正しいか確認 -->
<!-- 3. デバッグ出力を追加 -->
<eval token="debug_click">"Clicked: " + $row.fieldname$</eval>
<!-- ↑ クリックイベントが発火しているか確認 -->
</drilldown>
</table>
<!-- デバッグ表示 -->
<html>
<div>デバッグ: $debug_click$</div>
</html>
📊 トークンの使い分けチャート
┌─────────────────────────────────────────────────┐
│ トークンの使い分け判断フロー │
└─────────────────────────────────────────────────┘
↓
[ユーザー入力が必要?]
↓ YES ↓ NO
┌─────────────┐ ┌─────────────┐
│フォームトークン│ │カスタムトークン│
│$form.xxx$ │ │$xxx$ │
└─────────────┘ └─────────────┘
↓ ↓
[入力タイプ選択] [設定方法選択]
├─ dropdown ├─ <set>
├─ radio ├─ <eval>
├─ text └─ サーチ結果
├─ multiselect
└─ time
🎓 まとめ
✅ 学んだこと
- トークンの基本: 動的な値を保持する変数
- 3つの種類: デフォルト、カスタム、フォーム
-
設定方法:
<set>,<unset>,<condition> - 実践パターン: フォーム、ドリルダウン、連鎖
- デバッグ技術: 値の確認、トラブルシューティング
📌 重要ポイント
| 項目 | ポイント |
|---|---|
| 命名規則 | わかりやすい名前をつける |
| スコープ | form と通常トークンを使い分け |
| depends | 条件付き表示で動的UI |
| デバッグ | 常に値を確認できるパネルを用意 |
| 初期化 |
<init> で初期値を設定 |
📚 参考リンク
🎉 お疲れ様でした!トークンを使いこなして動的なダッシュボードを作りましょう!
さらに一緒に学びたい方はこちらもどうぞ!
🎨 SplunkクラシックでJS不要!HTML/CSS/SPLだけで作るオリジナルチャート
JavaScriptなしでHTML+CSS+SPL**だけで図形チャートを作ります!
📋 実装の全体像
┌─────────────────────────────────────────┐
│ Step1: インデックス作成 │
│ ├─ ルールと制約 │
│ └─ ベストプラクティス │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Step2: 外部データ準備 │
│ └─ CSV形式でデータ作成 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Step3: SPLでHTMLタグ生成 │
│ └─ eval + mvappend で動的HTML │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Step4: ダッシュボードXML │
│ └─ <html>パネルに埋め込み │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Step5: CSSスタイリング │
│ └─ インラインスタイル適用 │
└─────────────────────────────────────────┘
🗂️ Step1: インデックス作成のルールと制約
インデックスとは?
┌────────────────────────────────────────┐
│ インデックス = データの保管庫 │
│ │
│ 生データ → インデックス → 検索可能 │
│ │
│ [ログファイル] → [main] → SPL検索 │
└────────────────────────────────────────┘
インデックス作成の制約
| 項目 | 制約内容 | 理由 |
|---|---|---|
| 名前 | 小文字、数字、アンダースコアのみ | Splunk内部処理の仕様 |
| 予約語 | main, _internal, _audit等は使用不可 | システムインデックスとの競合 |
| 長さ | 最大255文字 | ファイルシステムの制限 |
| 作成後 | 名前の変更不可 | データ整合性のため |
| 削除 | 慎重に実施(データ消失) | 復旧不可 |
インデックス作成手順
方法A: Web UIから作成
┌─────────────────────────────────┐
│ 1. Settings クリック │
│ └→ Indexes クリック │
│ │
│ 2. New Index ボタン │
│ │
│ 3. 設定項目入力 │
│ ├─ Index Name: monitoring │
│ ├─ Max Size: 500MB │
│ └─ Retention: 7 days │
│ │
│ 4. Save ボタン │
└─────────────────────────────────┘
方法B: CLIから作成
# Splunkディレクトリに移動
cd $SPLUNK_HOME/bin
# インデックスを作成
./splunk add index monitoring -auth admin:password
# ↑ 'monitoring'という名前のインデックスを作成
# インデックスの最大サイズを設定
./splunk set index-maxsize 500 monitoring -auth admin:password
# ↑ 最大サイズを500MBに制限
# 設定を確認
./splunk list index monitoring -auth admin:password
# ↑ 作成したインデックスの設定を表示
方法C: indexes.confで作成
$SPLUNK_HOME/etc/apps/custom_monitoring/local/indexes.conf
# カスタム監視用インデックス定義
[monitoring]
# インデックス名(必須)
homePath = $SPLUNK_DB/monitoring/db
# データファイルの保存場所
coldPath = $SPLUNK_DB/monitoring/colddb
# 古いデータの保存場所
thawedPath = $SPLUNK_DB/monitoring/thaweddb
# 削除予定データの一時保存場所
maxTotalDataSizeMB = 500
# インデックスの最大サイズ(MB)
frozenTimePeriodInSecs = 604800
# データ保持期間(秒)7日 = 604800秒
enableDataIntegrityControl = 1
# データ整合性チェックを有効化
enableTsidxReduction = 1
# インデックスサイズ最適化を有効化
設定後の再起動
# Splunkを再起動して設定を反映
$SPLUNK_HOME/bin/splunk restart
📥 Step2: 外部データの準備
monitoring_data.csv
_time,device_name,status,temperature,response_time,location
2026-01-17 10:00:00,Server-A,OK,45,120,Tokyo
2026-01-17 10:00:00,Server-B,NG,78,350,Osaka
2026-01-17 10:00:00,Server-C,OK,52,95,Nagoya
2026-01-17 10:00:00,Server-D,WARNING,68,280,Fukuoka
データアップロード手順
┌──────────────────────────────────────┐
│ Settings → Add Data → Upload │
│ │
│ 1. ファイル選択 │
│ └→ monitoring_data.csv │
│ │
│ 2. Set Source Type │
│ └→ csv を選択 │
│ │
│ 3. Input Settings │
│ ├─ Host: localhost │
│ ├─ Index: monitoring (新規作成) │
│ └─ Source type: csv │
│ │
│ 4. Review → Submit │
└──────────────────────────────────────┘
ルックアップテーブルとして登録
transforms.conf
# ルックアップテーブル定義
[monitoring_lookup]
# ルックアップ名(SPLで使用する名前)
filename = monitoring_data.csv
# CSVファイル名を指定
max_matches = 1000
# 最大マッチ数を設定
min_matches = 1
# 最小マッチ数を設定
case_sensitive_match = false
# 大文字小文字を区別しない
props.conf
# ソースタイプ定義
[csv]
# CSVファイル用の設定
LOOKUP-monitoring = monitoring_lookup
# ルックアップテーブルを関連付け
INDEXED_EXTRACTIONS = csv
# CSVフィールドを自動抽出
SHOULD_LINEMERGE = false
# 複数行を結合しない
TIME_FORMAT = %Y-%m-%d %H:%M:%S
# 時刻フォーマットを指定
TIME_PREFIX = ^
# 時刻の開始位置を指定
🔍 Step3: SPLでHTMLタグ生成
基本SPL
index=monitoring sourcetype=csv
| eval status_code=case(
status=="OK", 0,
status=="WARNING", 1,
status=="NG", 2,
1==1, 3
)
| eval color=case(
status_code==0, "#4CAF50",
status_code==1, "#FFC107",
status_code==2, "#F44336",
1==1, "#9E9E9E"
)
| eval shape_class=case(
device_name=="Server-A", "square",
device_name=="Server-B", "circle",
device_name=="Server-C", "square",
device_name=="Server-D", "bubble",
1==1, "square"
)
| table device_name status color shape_class temperature response_time location
HTML生成SPL(完全版)
index=monitoring sourcetype=csv
`-- インデックスからCSVデータを検索`
| eval status_code=case(
status=="OK", 0,
`-- OKの場合は0`
status=="WARNING", 1,
`-- WARNINGの場合は1`
status=="NG", 2,
`-- NGの場合は2`
1==1, 3
`-- それ以外は3`
)
`-- ステータスを数値コードに変換`
| eval color=case(
status_code==0, "#4CAF50",
`-- OK時は緑色`
status_code==1, "#FFC107",
`-- WARNING時は黄色`
status_code==2, "#F44336",
`-- NG時は赤色`
1==1, "#9E9E9E"
`-- 不明時はグレー`
)
`-- ステータスに応じた色コードを設定`
| eval shape_class=case(
device_name=="Server-A", "square",
`-- Server-Aは四角`
device_name=="Server-B", "circle",
`-- Server-Bは丸`
device_name=="Server-C", "square",
`-- Server-Cは四角`
device_name=="Server-D", "bubble",
`-- Server-Dは吹き出し`
1==1, "square"
`-- デフォルトは四角`
)
`-- デバイスごとに図形クラスを決定`
| eval bubble_arrow=if(shape_class=="bubble",
"<div class=\"bubble-arrow\" style=\"border-top-color: " + color + ";\"></div>",
""
)
`-- 吹き出しの場合は三角形タグを追加`
| eval html_shape="<div class=\"shape-item\">" +
"<div class=\"shape shape-" + shape_class + "\" style=\"background-color: " + color + ";\">" +
bubble_arrow +
"</div>" +
"<div class=\"shape-label\">" +
"<div class=\"device-name\">" + device_name + "</div>" +
"<div class=\"status\">" + status + "</div>" +
"<div class=\"info\">温度: " + temperature + "℃</div>" +
"<div class=\"info\">応答: " + response_time + "ms</div>" +
"<div class=\"info\">場所: " + location + "</div>" +
"</div>" +
"</div>"
`-- HTMLタグ文字列を1つのフィールドに結合`
| stats values(html_shape) as html_shapes
`-- 全てのHTML要素を配列として収集`
| eval html_output="<div class=\"shape-grid\">" + mvjoin(html_shapes, "") + "</div>"
`-- グリッドコンテナで全体をラップ`
| table html_output
`-- HTML文字列のみを出力`
📊 Step4: ダッシュボードXML
dashboard.xml(完全版)
<!-- Splunk Simple XMLダッシュボード -->
<dashboard>
<!-- ダッシュボードのタイトル -->
<label>🎨 監視状態ダッシュボード(JS不要版)</label>
<!-- ダッシュボードの説明 -->
<description>HTML + CSS + SPLのみで実装</description>
<!-- CSSスタイルを定義 -->
<row>
<panel>
<html>
<style>
/* ビジュアライゼーション全体のコンテナ */
.custom-shapes-viz {
width: 100%;
/* 幅を100%に設定 */
padding: 20px;
/* 内側の余白を20px */
background-color: #f5f5f5;
/* 背景色を薄いグレーに */
box-sizing: border-box;
/* パディングを含めたサイズ計算 */
}
/* 図形を配置するグリッドコンテナ */
.shape-grid {
display: grid;
/* CSS Gridレイアウトを使用 */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
/* 自動でカラム数を調整 */
gap: 30px;
/* グリッドアイテム間の隙間 */
padding: 20px;
/* 内側の余白 */
}
/* 各図形アイテムのコンテナ */
.shape-item {
display: flex;
/* Flexboxレイアウト */
flex-direction: column;
/* 縦方向に配置 */
align-items: center;
/* 水平方向に中央揃え */
gap: 15px;
/* 子要素間の隙間 */
padding: 15px;
/* 内側の余白 */
background: white;
/* 背景色を白に */
border-radius: 12px;
/* 角を12pxで丸める */
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
/* 影を追加 */
transition: transform 0.3s ease;
/* アニメーション設定 */
}
/* ホバー時のエフェクト */
.shape-item:hover {
transform: translateY(-5px);
/* 上に5px移動 */
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
/* 影を濃く */
}
/* 図形の基本スタイル */
.shape {
width: 100px;
/* 幅を100px */
height: 100px;
/* 高さを100px */
display: flex;
/* Flexboxレイアウト */
align-items: center;
/* 垂直方向に中央揃え */
justify-content: center;
/* 水平方向に中央揃え */
position: relative;
/* 相対位置指定 */
transition: all 0.3s ease;
/* アニメーション */
}
/* 四角形のスタイル */
.shape-square {
border-radius: 8px;
/* 角を8pxで丸める */
}
/* 円形のスタイル */
.shape-circle {
border-radius: 50%;
/* 角を50%で丸めて円形に */
}
/* 吹き出しのスタイル */
.shape-bubble {
border-radius: 12px;
/* 角を12pxで丸める */
}
/* 吹き出しの三角形部分 */
.bubble-arrow {
position: absolute;
/* 絶対位置指定 */
bottom: -15px;
/* 下に15px移動 */
left: 50%;
/* 左から50%の位置 */
transform: translateX(-50%);
/* 中央に配置 */
width: 0;
/* 幅を0に */
height: 0;
/* 高さを0に */
border-left: 15px solid transparent;
/* 左のボーダー */
border-right: 15px solid transparent;
/* 右のボーダー */
border-top: 15px solid #4CAF50;
/* 上のボーダーで三角形作成 */
}
/* ラベル全体のスタイル */
.shape-label {
text-align: center;
/* テキストを中央揃え */
width: 100%;
/* 幅を100% */
}
/* デバイス名のスタイル */
.device-name {
font-weight: bold;
/* 太字 */
font-size: 16px;
/* フォントサイズ */
color: #333;
/* テキスト色 */
margin-bottom: 5px;
/* 下に余白 */
}
/* ステータステキストのスタイル */
.status {
font-size: 14px;
/* フォントサイズ */
font-weight: 600;
/* セミボールド */
color: #666;
/* テキスト色 */
margin-bottom: 8px;
/* 下に余白 */
padding: 4px 12px;
/* パディング */
background-color: #f0f0f0;
/* 背景色 */
border-radius: 12px;
/* 角を丸める */
display: inline-block;
/* インラインブロック */
}
/* 詳細情報のスタイル */
.info {
font-size: 12px;
/* フォントサイズ */
color: #888;
/* テキスト色 */
margin: 2px 0;
/* 余白 */
}
/* レスポンシブ: タブレット */
@media (max-width: 768px) {
.shape-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
/* カラム幅を縮小 */
gap: 20px;
/* 隙間を縮小 */
}
.shape {
width: 80px;
/* 図形を縮小 */
height: 80px;
/* 図形を縮小 */
}
}
/* レスポンシブ: スマホ */
@media (max-width: 480px) {
.shape-grid {
grid-template-columns: 1fr;
/* 1カラム表示 */
gap: 15px;
/* 隙間を縮小 */
}
}
</style>
</html>
</panel>
</row>
<!-- メインビジュアライゼーション -->
<row>
<panel>
<title>🎨 デバイス状態ビジュアライゼーション</title>
<html>
<div class="custom-shapes-viz">
<!-- SPLで生成されたHTMLがここに表示される -->
$html_content$
</div>
</html>
</panel>
</row>
<!-- HTML生成用のサーチ -->
<search id="shape_search">
<query>
index=monitoring sourcetype=csv
| eval status_code=case(status=="OK", 0, status=="WARNING", 1, status=="NG", 2, 1==1, 3)
| eval color=case(status_code==0, "#4CAF50", status_code==1, "#FFC107", status_code==2, "#F44336", 1==1, "#9E9E9E")
| eval shape_class=case(device_name=="Server-A", "square", device_name=="Server-B", "circle", device_name=="Server-C", "square", device_name=="Server-D", "bubble", 1==1, "square")
| eval bubble_arrow=if(shape_class=="bubble", "<div class=\"bubble-arrow\" style=\"border-top-color: " + color + ";\"></div>", "")
| eval html_shape="<div class=\"shape-item\"><div class=\"shape shape-" + shape_class + "\" style=\"background-color: " + color + ";\">" + bubble_arrow + "</div><div class=\"shape-label\"><div class=\"device-name\">" + device_name + "</div><div class=\"status\">" + status + "</div><div class=\"info\">温度: " + temperature + "℃</div><div class=\"info\">応答: " + response_time + "ms</div><div class=\"info\">場所: " + location + "</div></div></div>"
| stats values(html_shape) as html_shapes
| eval html_content="<div class=\"shape-grid\">" + mvjoin(html_shapes, "") + "</div>"
| table html_content
</query>
<!-- サーチの時間範囲 -->
<earliest>-24h@h</earliest>
<latest>now</latest>
<!-- トークンに結果を設定 -->
<done>
<set token="html_content">$result.html_content$</set>
</done>
</search>
<!-- 詳細テーブル表示 -->
<row>
<panel>
<title>📋 詳細データテーブル</title>
<table>
<search>
<query>
index=monitoring sourcetype=csv
| eval status_code=case(status=="OK", 0, status=="WARNING", 1, status=="NG", 2, 1==1, 3)
| table device_name status temperature response_time location _time
| sort device_name
</query>
<earliest>-24h@h</earliest>
<latest>now</latest>
</search>
<!-- テーブルのオプション -->
<option name="drilldown">none</option>
<option name="wrap">true</option>
<option name="rowNumbers">true</option>
<option name="dataOverlayMode">none</option>
<option name="count">10</option>
</table>
</panel>
</row>
</dashboard>
🚨 Step5: 困ったときの対応
❌ 問題1: インデックスが作成できない
┌────────────────────────────────────┐
│ エラー: Index name contains │
│ invalid characters │
└────────────────────────────────────┘
原因チェックリスト:
| makeresults
| eval index_name="monitoring-data"
`-- ハイフンはNG(アンダースコアを使用)`
| eval correct_name="monitoring_data"
`-- 正しい命名規則`
| eval check=if(match(index_name, "^[a-z0-9_]+$"), "OK", "NG")
`-- 正規表現でチェック`
| table index_name correct_name check
解決策:
# 正しいインデックス名で再作成
cd $SPLUNK_HOME/bin
./splunk add index monitoring_data -auth admin:password
# ↑ アンダースコアを使用
# 確認
./splunk list index | grep monitoring
# ↑ 作成されたインデックスを確認
❌ 問題2: データがインデックスに入らない
┌────────────────────────────────────┐
│ 検索結果: 0 events │
└────────────────────────────────────┘
診断SPL:
| rest /services/data/indexes
`-- 全インデックスの情報を取得`
| search title="monitoring"
`-- 対象インデックスに絞り込み`
| table title totalEventCount currentDBSizeMB
`-- イベント数とサイズを確認`
| rest /services/data/inputs/monitor
`-- 監視中の入力ソースを確認`
| table title disabled index
`-- 有効/無効状態を確認`
解決策A: inputs.confを確認
# 設定ファイルを確認
cat $SPLUNK_HOME/etc/apps/custom_monitoring/local/inputs.conf
# 正しい設定例
[monitor:///opt/monitoring/data/*.csv]
disabled = false
# ↑ disabledがfalseになっているか確認
sourcetype = csv
index = monitoring
# ↑ インデックス名が正しいか確認
解決策B: 手動でデータを再投入
# Splunk CLIでデータを追加
cd $SPLUNK_HOME/bin
./splunk add oneshot /path/to/monitoring_data.csv -sourcetype csv -index monitoring -auth admin:password
# ↑ ファイルパスを確認してコマンド実行
❌ 問題3: HTMLが表示されない
┌────────────────────────────────────┐
│ パネルが空白 │
└────────────────────────────────────┘
診断SPL:
index=monitoring sourcetype=csv
| eval html_test="<div>テスト</div>"
`-- シンプルなHTMLタグで確認`
| table html_test
`-- 出力を確認`
index=monitoring sourcetype=csv
| stats count
`-- データ件数を確認`
解決策: トークンの確認
<!-- dashboard.xmlを修正 -->
<search id="shape_search">
<query>
index=monitoring sourcetype=csv
| head 1
| eval html_content="<div style='color: red;'>テスト表示</div>"
| table html_content
</query>
<done>
<!-- トークン名が一致しているか確認 -->
<set token="html_content">$result.html_content$</set>
</done>
</search>
<html>
<div class="custom-shapes-viz">
<!-- トークンが正しく参照されているか確認 -->
$html_content$
</div>
</html>
❌ 問題4: 色が反映されない
┌────────────────────────────────────┐
│ 全て同じ色で表示される │
└────────────────────────────────────┘
診断SPL:
index=monitoring sourcetype=csv
| eval status_debug=status
`-- ステータス値を確認`
| eval color_debug=case(
status=="OK", "#4CAF50",
status=="WARNING", "#FFC107",
status=="NG", "#F44336",
1==1, "#9E9E9E"
)
`-- 色の割り当てをデバッグ`
| table device_name status status_debug color_debug
`-- 全フィールドを表示して確認`
解決策: ステータス値の正規化
index=monitoring sourcetype=csv
| eval status=upper(trim(status))
`-- 大文字に統一&空白削除`
| eval status=case(
status=="OK" OR status=="NORMAL", "OK",
status=="WARN" OR status=="WARNING", "WARNING",
status=="ERROR" OR status=="NG", "NG",
1==1, "UNKNOWN"
)
`-- ステータス値を標準化`
| eval color=case(
status=="OK", "#4CAF50",
status=="WARNING", "#FFC107",
status=="NG", "#F44336",
1==1, "#9E9E9E"
)
| table device_name status color
❌ 問題5: パーミッションエラー
┌────────────────────────────────────┐
│ Error: Insufficient permissions │
└────────────────────────────────────┘
原因:
- ユーザーのロールにインデックスへのアクセス権限がない
解決策:
# ロールの確認
cd $SPLUNK_HOME/bin
./splunk list role
# ↑ 利用可能なロールを表示
# ユーザーにロールを追加
./splunk edit user username -role power -auth admin:password
# ↑ usernameをpowerロールに追加
# インデックスの権限を確認
./splunk list index monitoring -auth admin:password
# ↑ アクセス権限を確認
authorize.confで権限設定:
# $SPLUNK_HOME/etc/apps/custom_monitoring/local/authorize.conf
# powerロールに権限を付与
[role_power]
# インデックスの読み取り権限
srchIndexesAllowed = monitoring;main;_*
# インデックスの書き込み権限
srchIndexesDefault = monitoring;main
📈 動作確認フロー図
┌─────────────────────────────────────────┐
│ 1. データ確認 │
│ index=monitoring | stats count │
│ └→ 件数が0なら: データ投入を確認 │
└─────────────────────────────────────────┘
↓ OK
┌─────────────────────────────────────────┐
│ 2. フィールド確認 │
│ index=monitoring | table * │
│ └→ 必要なフィールドが無いなら: │
│ props.confを確認 │
└─────────────────────────────────────────┘
↓ OK
┌─────────────────────────────────────────┐
│ 3. ステータス確認 │
│ | eval color=case(...) │
│ └→ 色が正しく割り当てられているか │
└─────────────────────────────────────────┘
↓ OK
┌─────────────────────────────────────────┐
│ 4. HTML生成確認 │
│ | eval html_shape="<div>..." │
│ └→ HTMLタグが正しく生成されているか │
└─────────────────────────────────────────┘
↓ OK
┌─────────────────────────────────────────┐
│ 5. ダッシュボード表示確認 │
│ dashboard.xml の $token$ が正しいか │
└─────────────────────────────────────────┘
🎯 まとめ
✅ できるようになったこと
- インデックスの作成(ルールと制約の理解)
- 外部CSVデータのインデックス投入
- SPLでのHTML動的生成
- CSS+HTMLのみでの図形描画
- JavaScriptなしのカスタムビジュアライゼーション
- トラブルシューティングの基本フロー
📌 重要ポイント
| 項目 | ポイント |
|---|---|
| インデックス名 | 小文字+数字+アンダースコアのみ |
| SPL | evalでHTML文字列を動的生成 |
| トークン |
$token$でXMLに値を埋め込み |
| CSS | インラインスタイルで色を制御 |
| デバッグ | 段階的に確認して原因を特定 |
📚 参考コマンド集
# インデックス一覧表示
$SPLUNK_HOME/bin/splunk list index
# インデックス作成
$SPLUNK_HOME/bin/splunk add index <index_name>
# インデックス削除(注意!)
$SPLUNK_HOME/bin/splunk remove index <index_name>
# データ投入
$SPLUNK_HOME/bin/splunk add oneshot <file_path> -index <index_name>
# Splunk再起動
$SPLUNK_HOME/bin/splunk restart
# ログ確認
tail -f $SPLUNK_HOME/var/log/splunk/splunkd.log
🎉 お疲れ様でした!JavaScriptなしでもSplunkで動的なビジュアライゼーションが作れます!
もっと学びたい方はこちらをどうぞ!
SPL(Search Processing Language)完全解説:新人エンジニア向け
🎯 SPLとは?
Search Processing Language = Splunkでデータを検索・加工・集計するための専用言語
イメージ:
- SQL = データベース用の言語
- SPL = Splunk用の言語
📌 SPLの基本構造
パイプライン方式
データ取得 | 絞り込み | 加工 | 集計 | 表示
特徴: |(パイプ)で処理を繋げていく
🔍 今回使った SPL を完全解説
完全版コード
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
1️⃣ index=20260115tra
📖 意味
データソース(index)を指定
🔧 構文
index=<インデックス名>
💡 新人向け解説
- Splunkに取り込んだデータの「置き場所」
- CSVをアップロードすると自動で index が作られる
- 複数のindexがある場合、ここで絞り込む
📊 例
index=web_logs # Webサーバーのログ
index=app_errors # アプリケーションエラー
index=20260115tra # 今回の道路データ
2️⃣ | where like(location,"%湾岸%")
📖 意味
location列に「湾岸」という文字を含むデータだけ抽出
🔧 構文
| where like(<フィールド名>, "<パターン>")
💡 新人向け解説
-
%はワイルドカード(何でもマッチ) - SQL の
LIKEと同じ -
%湾岸%= 「前後に何があっても湾岸を含めばOK」
📊 マッチ例
| location | マッチするか? |
|---|---|
| 首都高 湾岸線 東京 | ✅ マッチ |
| 湾岸道路 | ✅ マッチ |
| 東京湾岸 | ✅ マッチ |
| 東名高速 | ❌ マッチしない |
🎯 応用パターン
# 前方一致
| where like(location, "首都高%")
# 後方一致
| where like(location, "%高速")
# 完全一致
| where location="首都高 湾岸線"
# 複数条件(OR)
| where like(location, "%湾岸%") OR like(location, "%東名%")
# 複数条件(AND)
| where like(location, "%首都高%") AND like(location, "%湾岸%")
3️⃣ | sort -_time
📖 意味
_time(時刻)フィールドで降順ソート(新しい順)
🔧 構文
| sort <-フィールド名> # 降順(大→小)
| sort <フィールド名> # 昇順(小→大)
💡 新人向け解説
-
-がつくと降順 -
-がないと昇順 -
_timeは Splunk の特殊フィールド(全データに自動付与)
📊 ソート結果イメージ
元データ:
_time: 2024-01-15 18:30 result: OK
_time: 2024-01-15 18:40 result: NG
_time: 2024-01-15 18:35 result: OK
sort -_time 後:
_time: 2024-01-15 18:40 result: NG ← 最新
_time: 2024-01-15 18:35 result: OK
_time: 2024-01-15 18:30 result: OK
🎯 応用パターン
# 昇順(古い順)
| sort _time
# 複数フィールドでソート
| sort -_time, location
# 数値フィールドでソート
| sort -count
# ソート後の件数制限
| sort -_time | head 10
4️⃣ | head 1
📖 意味
上から1件だけ取得(最新1件)
🔧 構文
| head <件数>
💡 新人向け解説
- 最重要コマンド!
-
sort -_timeと組み合わせて「最新1件」を取得 - これがないと過去の全データが対象になる
📊 動作イメージ
head 1 なし:
2024-01-15 18:40 NG ← 最新
2024-01-15 18:35 OK
2024-01-15 18:30 NG
2024-01-15 18:25 OK
↓
全部が処理対象(NGが2件ある)
head 1 あり:
2024-01-15 18:40 NG ← これだけ
↓
最新の1件だけ処理(NGが1件)
🎯 応用パターン
# 最新5件
| head 5
# 最古の1件(昇順ソート + head)
| sort _time | head 1
# 最新10件の次の10件(offset)
| sort -_time | head 20 | tail 10
5️⃣ | eval is_ng=if(result="NG",1,0)
📖 意味
新しいフィールド is_ng を作成し、条件分岐で値を設定
🔧 構文
| eval <新フィールド名>=if(<条件>, <真の値>, <偽の値>)
💡 新人向け解説
-
eval= 計算・加工用のコマンド -
if()= 3項演算子(プログラミングの? :と同じ) - 結果を数値化することでトークン判定が簡単になる
📊 動作イメージ
入力データ:
result: NG
処理:
if(result="NG", 1, 0)
↓
result が "NG" か? → YES
↓
1 を返す
出力データ:
result: NG
is_ng: 1
🎯 応用パターン
複数条件(case)
| eval status=case(
result="NG", "赤",
result="WARN", "黄",
result="OK", "緑",
1=1, "不明"
)
計算
| eval total=price * quantity
| eval discount_price=price * 0.9
文字列操作
| eval full_name=first_name." ".last_name
| eval upper_location=upper(location)
日時操作
| eval hour=strftime(_time, "%H")
| eval date=strftime(_time, "%Y-%m-%d")
NULL チェック
| eval is_empty=if(isnull(field), "空", "有")
🎓 SPL の処理順序(重要)
今回のクエリを分解
index=20260115tra # ① データ取得
| where like(location,"%湾岸%") # ② 絞り込み
| sort -_time # ③ ソート
| head 1 # ④ 最新1件
| eval is_ng=if(result="NG",1,0) # ⑤ 加工
📊 各段階でのデータ変化
① index=20260115tra
┌─────────────────────────────┐
│ 100万件のデータ │
└─────────────────────────────┘
② | where like(location,"%湾岸%")
┌─────────────────────────────┐
│ 1,000件(湾岸関連のみ) │
└─────────────────────────────┘
③ | sort -_time
┌─────────────────────────────┐
│ 1,000件(新しい順に並び替え)│
└─────────────────────────────┘
④ | head 1
┌─────────────────────────────┐
│ 1件(最新のみ) │
└─────────────────────────────┘
⑤ | eval is_ng=if(result="NG",1,0)
┌─────────────────────────────┐
│ 1件(is_ng フィールド追加) │
│ location: 首都高湾岸線 │
│ result: NG │
│ is_ng: 1 │
└─────────────────────────────┘
🔧 よく使う SPL コマンド一覧
データ取得系
| コマンド | 意味 | 例 |
|---|---|---|
index= |
データソース指定 | index=web_logs |
source= |
ファイル指定 | source="/var/log/app.log" |
sourcetype= |
データ形式指定 | sourcetype=access_combined |
絞り込み系
| コマンド | 意味 | 例 |
|---|---|---|
where |
条件絞り込み | where status=200 |
search |
キーワード検索 | search error |
like |
パターンマッチ | like(field, "%keyword%") |
in |
リスト照合 | where status in(200,201,204) |
並び替え・抽出系
| コマンド | 意味 | 例 |
|---|---|---|
sort |
ソート | sort -_time |
head |
上からN件 | head 10 |
tail |
下からN件 | tail 10 |
dedup |
重複削除 | dedup user_id |
加工系
| コマンド | 意味 | 例 |
|---|---|---|
eval |
フィールド作成・計算 | eval total=price*qty |
rex |
正規表現で抽出 | rex field=_raw "(?<ip>\d+\.\d+\.\d+\.\d+)" |
replace |
文字列置換 | eval status=replace(status,"OK","正常") |
集計系
| コマンド | 意味 | 例 |
|---|---|---|
stats |
集計 | stats count by status |
count |
件数 | stats count as total |
sum |
合計 | stats sum(amount) by user |
avg |
平均 | stats avg(response_time) |
max / min
|
最大/最小 | stats max(price) |
時系列系
| コマンド | 意味 | 例 |
|---|---|---|
timechart |
時系列グラフ | timechart count by status |
bin |
時間バケット | bin _time span=1h |
earliest / latest
|
時刻範囲 | earliest=-24h |
🎯 実践パターン集
パターン1:最新のステータスを取得
index=mydata
| where user_id=12345
| sort -_time
| head 1
| table user_id, status, _time
パターン2:エラー件数を集計
index=app_logs
| where level="ERROR"
| stats count by error_type
| sort -count
パターン3:時間帯別の平均応答時間
index=web_logs
| eval hour=strftime(_time, "%H")
| stats avg(response_time) by hour
| sort hour
パターン4:上位10ユーザー
index=access_logs
| stats count by user_id
| sort -count
| head 10
パターン5:条件付き集計
index=sales
| stats
count as total,
count(eval(status="completed")) as success,
count(eval(status="failed")) as failed
by store_id
⚠️ よくあるミス
ミス1:head の位置
❌ NG
index=mydata
| head 1
| sort -_time
↓
ランダムな1件をソートしても意味ない
✅ OK
index=mydata
| sort -_time
| head 1
↓
ソートしてから最新1件
ミス2:大文字小文字
❌ NG
| where result="ng"
↓
データが "NG" なのでマッチしない
✅ OK
| where result="NG"
または
| where lower(result)="ng"
ミス3:where と search の混同
# where:フィールド名を指定
| where status=200
# search:全文検索
| search "status=200"
🚀 デバッグテクニック
テクニック1:途中結果を確認
index=20260115tra
| where like(location,"%湾岸%")
| table location, _time, result # ← 途中で確認
テクニック2:件数確認
index=20260115tra
| where like(location,"%湾岸%")
| stats count # ← 何件あるか確認
テクニック3:フィールド一覧
index=20260115tra
| head 1
| table * # ← 全フィールドを表示
📚 学習ロードマップ
レベル1(初級)
- ✅
index,where,sort,head - ✅
evalの基本(if, 四則演算) - ✅
stats count
レベル2(中級)
- 🔶
statsの高度な使い方(sum, avg, max, min) - 🔶
timechart - 🔶
rex(正規表現)
レベル3(上級)
- 🔥
join(結合) - 🔥
eventstats,streamstats - 🔥
lookup(外部データ参照)
📝 まとめ
| コマンド | 役割 | 重要度 |
|---|---|---|
index= |
データ取得 | ⭐⭐⭐ |
where |
絞り込み | ⭐⭐⭐ |
sort |
並び替え | ⭐⭐⭐ |
head |
件数制限 | ⭐⭐⭐ |
eval |
加工 | ⭐⭐⭐ |
stats |
集計 | ⭐⭐ |
table |
表示 | ⭐⭐ |
SPL は書いて覚える! まずは今回のクエリを理解して、1行ずつ変えて実験してみよう 🚀
Splunk Dashboard で「4つのボックスを線でつなぐ」フロー型UI実装ガイド
🎯 この記事で作るもの
道路ステータスを 横並び + 線でつなぐ フロー型ダッシュボード
🟢 首都高湾岸線 ━━ 🔴 国道16号 ━━ 🟢 東名高速 ━━ 🟢 中央高速
(OK) (NG) (OK) (OK)
📌 実装の全体像
3つの要素で構成
| 要素 | 役割 | 技術 |
|---|---|---|
| ① SPL検索 | データ取得 + NG判定 | Splunk Search Processing Language |
| ② トークン | 検索結果をHTML側に渡す | Dashboard Token機能 |
| ③ HTML+CSS | ボックスと線の描画 | Flexbox レイアウト |
🔍 実装解説:3ステップ
ステップ1️⃣:SPL検索で「最新1件のNG判定」
📝 コード全文
<search id="box1_search">
<query>
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box1_color">red</set>
<set token="box1_state">NG</set>
</condition>
<condition>
<set token="box1_color">green</set>
<set token="box1_state">OK</set>
</condition>
</done>
</search>
🔑 SPL 1行ずつ解説
index=20260115tra
意味: データソースを指定(CSVから取り込んだindex)
新人向け: Splunkにアップロードしたデータの「置き場所」を指定している
| where like(location,"%湾岸%")
意味: location列に「湾岸」を含むデータだけ抽出
新人向け: % は SQL の LIKE と同じワイルドカード(前後に何があってもOK)
| sort -_time
意味: 時刻の降順でソート(新しい順)
新人向け: - がつくと降順、つかないと昇順
| head 1
意味: 最新の1件だけ取得
新人向け: ここが最重要! 過去の全データではなく「最新の状態」だけを見る
| eval is_ng=if(result="NG",1,0)
意味: result列が"NG"なら1、それ以外なら0
新人向け: 条件分岐して数値化(後でトークン判定に使う)
🎯 なぜ head 1 が必須なのか?
| パターン | 結果 | 理由 |
|---|---|---|
❌ head 1 なし |
過去のNG全部が影響 | 最新がOKでも昔のNGで赤になる |
✅ head 1 あり |
最新の状態だけ反映 | 現在の状態 を正確に表示 |
ステップ2️⃣:トークンで検索結果をHTML側に渡す
📝 トークン設定部分
<done>
<condition match="$result.is_ng$=1">
<set token="box1_color">red</set>
<set token="box1_state">NG</set>
</condition>
<condition>
<set token="box1_color">green</set>
<set token="box1_state">OK</set>
</condition>
</done>
🔑 仕組み解説
| 要素 | 意味 |
|---|---|
<done> |
検索が完了したら実行 |
$result.is_ng$ |
SPLで作った is_ng の値を参照 |
match="...=1" |
条件に一致するか判定 |
<set token="box1_color">red</set> |
トークン変数に値を格納 |
🎨 トークンの使い方
設定側(XML)
<set token="box1_color">red</set>
利用側(HTML)
<div class="box $box1_color$">
結果(レンダリング後)
<div class="box red">
ステップ3️⃣:HTML + CSS でボックスと線を描画
📝 HTML構造
<div class="flow">
<div class="box $box1_color$">
<div class="name">首都高 湾岸線</div>
<div class="state">$box1_state$</div>
</div>
<div class="line"></div>
<div class="box $box2_color$">
<div class="name">国道16号</div>
<div class="state">$box2_state$</div>
</div>
<div class="line"></div>
<!-- 以下、BOX3, BOX4 も同様 -->
</div>
🎨 CSS 設計のポイント
① 横並びレイアウト(Flexbox)
.flow{
display: flex; /* 横並び有効化 */
align-items: center; /* 縦方向の中央揃え */
padding: 30px;
}
新人向け: display: flex をつけると、子要素が自動で横並びになる
② ボックスのデザイン
.box{
width: 200px;
height: 110px;
border-radius: 14px; /* 角丸 */
display: flex;
flex-direction: column; /* 中身を縦並び */
align-items: center; /* 横方向の中央揃え */
justify-content: center; /* 縦方向の中央揃え */
font-weight: bold;
color: white;
}
新人向け: ボックス自体も Flexbox にして、内部のテキストを中央配置
③ 色の切り替え(クラス)
.green { background: #4CAF50; } /* 緑 */
.red { background: #E53935; } /* 赤 */
HTML側でクラス名が切り替わる仕組み:
<!-- トークンが "green" の場合 -->
<div class="box green">
<!-- トークンが "red" の場合 -->
<div class="box red">
④ 線のデザイン
.line{
width: 60px;
height: 6px;
background: #999;
margin: 0 10px; /* 左右に余白 */
}
新人向け: 実体は「細長いグレーの四角」だが、ボックスと並べると線に見える
🏗️ 全体の組み立て順序
設計フロー
1. SPL検索で最新1件を取得
↓
2. is_ng=1 か 0 かを判定
↓
3. トークンに color と state を格納
↓
4. HTML側でトークンを展開
↓
5. CSS でスタイル適用
↓
6. ブラウザに表示
🎯 各ボックスに ID をつける理由
❌ 全部同じIDだとダメな理由
<!-- 全部 box_search だと -->
<search id="box_search">...</search>
<search id="box_search">...</search> <!-- 上書きされる -->
結果: 最後の検索結果だけが全ボックスに反映される
✅ 正しい実装(個別ID)
<search id="box1_search"> <!-- 湾岸線専用 -->
<search id="box2_search"> <!-- 国道16号専用 -->
<search id="box3_search"> <!-- 東名専用 -->
<search id="box4_search"> <!-- 中央高速専用 -->
ポイント: 1ボックス = 1検索 = 1セットのトークン
📊 データフロー図
┌─────────────────┐
│ index=20260115tra│
│ location: 湾岸 │
│ _time: 18:40 │
│ result: NG │
└────────┬────────┘
│ where like(location,"%湾岸%")
↓
┌─────────────────┐
│ 湾岸線のデータ │
│ (全期間) │
└────────┬────────┘
│ sort -_time | head 1
↓
┌─────────────────┐
│ 最新1件 │
│ result: NG │
└────────┬────────┘
│ eval is_ng=if(result="NG",1,0)
↓
┌─────────────────┐
│ is_ng: 1 │
└────────┬────────┘
│ <condition match="$result.is_ng$=1">
↓
┌─────────────────┐
│ box1_color: red │
│ box1_state: NG │
└────────┬────────┘
│ $box1_color$ → HTML展開
↓
┌─────────────────┐
│ <div class="box red">
│ NG
│ </div>
└─────────────────┘
🔧 トラブルシューティング
Q1: ボックスが縦並びになる
原因: .flow に display: flex がない
解決:
.flow {
display: flex; /* これを追加 */
}
Q2: 全部緑のままになる
原因: トークンが設定されていない
デバッグ方法:
<!-- 検索結果を確認 -->
<search id="box1_search">
<query>
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
| table location, _time, result, is_ng <!-- 追加 -->
</query>
</search>
確認ポイント:
- is_ng が 1 になっているか?
- result が "NG"(大文字)になっているか?
Q3: 線が表示されない
原因: <div class="line"/> の書き方
修正:
<!-- ❌ NG(Classic Dashboardでは動かない場合がある) -->
<div class="line"/>
<!-- ✅ OK -->
<div class="line"></div>
🚀 発展課題
レベル1:WARN(黄色)を追加
| eval is_ng=case(
result="NG", 2,
result="WARN", 1,
1=1, 0
)
<condition match="$result.is_ng$=2">
<set token="box1_color">red</set>
</condition>
<condition match="$result.is_ng$=1">
<set token="box1_color">yellow</set>
</condition>
<condition>
<set token="box1_color">green</set>
</condition>
レベル2:NG件数を表示
| stats
latest(_time) as latest_time,
latest(result) as latest_result,
count(eval(result="NG")) as ng_count
by location
| eval is_ng=if(latest_result="NG",1,0)
<div class="state">$box1_state$ ($box1_ng_count$件)</div>
レベル3:検索を1本に統合(上級)
index=20260115tra
| eventstats latest(_time) as latest_time by location
| where _time=latest_time
| eval is_ng=if(result="NG",1,0)
| eval road=case(
like(location,"%湾岸%"), "box1",
like(location,"%国道16号%"), "box2",
like(location,"%東名%"), "box3",
like(location,"%中央高速%"), "box4"
)
| stats values(is_ng) as is_ng by road
📝 まとめ
| 技術 | 役割 | 重要ポイント |
|---|---|---|
| SPL | データ取得 |
sort -_time | head 1 で最新1件 |
| トークン | データ受け渡し | 1ボックス=1セット |
| Flexbox | レイアウト |
display: flex で横並び |
| CSS クラス | 色切り替え |
.red .green
|
🎓 新人エンジニアへのアドバイス
- まず動かす:コピペして動作確認
- 1つずつ変える:色・文言・検索条件を変えて理解
-
デバッグ癖をつける:
| tableでデータ確認 - コメントを書く:自分の言葉で説明を残す
Splunk Dashboard で動的な色変更とアニメーション実装【完全解説】
🎯 この記事で作るもの
国道16号のステータスに応じて ボックスと線の色が自動で変わり、NG時は赤く点滅 するダッシュボード
🟢 首都高湾岸線 ━━ 🔴 国道16号 ━━ 🟢 東名高速 ━━ 🟢 中央高速
(OK) 赤点滅 (NG) 赤点滅 (OK) (OK)
📌 実装の3つの特徴
| 特徴 | 説明 |
|---|---|
| ① JSなし | JavaScriptを使わずトークンのみで実装 |
| ② 動的色変更 | SPL検索結果でリアルタイムに色が変わる |
| ③ CSSアニメ | NG時に赤く脈打つ視覚効果 |
🏗️ アーキテクチャ全体像
┌─────────────────────────────────────┐
│ SPL検索(NG判定) │
│ index=20260115tra │
│ → 国道16号のNG件数をカウント │
└──────────┬──────────────────────────┘
│
↓ 検索完了時
┌─────────────────────────────────────┐
│ <done> トークン設定 │
│ ng > 0 → red / それ以外 → green │
└──────────┬──────────────────────────┘
│
↓ トークン展開
┌─────────────────────────────────────┐
│ HTML + CSS │
│ class="box $box16_color$" │
│ → class="box red" に変換される │
└─────────────────────────────────────┘
📝 実装解説:3つのパート
Part 1️⃣:SPL検索でNG判定
完全版コード
<search id="ng_search">
<query>
index=20260115tra
| eval route=if(like(location,"%国道16号%"),"国道16号",null())
| where isnotnull(route)
| stats sum(eval(result="NG")) as ng
</query>
<earliest>0</earliest>
<latest>now</latest>
<done>
<condition match="$result.ng$ > 0">
<set token="box16_color">red</set>
<set token="box16_state">NG</set>
<set token="line_color">red</set>
</condition>
<condition>
<set token="box16_color">green</set>
<set token="box16_state">OK</set>
<set token="line_color">gray</set>
</condition>
</done>
</search>
🔍 SPL 1行ずつ解説
① データ取得
index=20260115tra
意味: CSVから取り込んだデータソースを指定
② 道路フィールド作成
| eval route=if(like(location,"%国道16号%"),"国道16号",null())
意味: location列に「国道16号」が含まれる場合のみ route フィールドを作成
詳細:
-
like(location,"%国道16号%")→ 部分一致検索 -
"国道16号"→ マッチした場合の値 -
null()→ マッチしなかった場合は null
データ変化:
| location | route |
|---|---|
| 国道16号 相模原市 | 国道16号 |
| 首都高 湾岸線 | null |
| 東名高速 | null |
③ 国道16号のみ抽出
| where isnotnull(route)
意味: route が null でないデータだけ残す
結果: 国道16号のデータだけになる
④ NG件数をカウント
| stats sum(eval(result="NG")) as ng
意味: result列が"NG"の件数を合計
仕組み:
-
eval(result="NG")→ NGなら1、それ以外は0 -
sum(...)→ 1を全部足す -
as ng→ 結果を ng という名前にする
例:
| result | eval(result="NG") |
|---|---|
| NG | 1 |
| OK | 0 |
| NG | 1 |
| 合計 | 2 |
🎯 なぜ sum(eval(...)) を使うのか?
比較:他の方法との違い
| 方法 | 結果 | 問題点 |
|---|---|---|
count |
全件数 | NGもOKもカウントされる |
count(eval(...)) |
エラーになる | Splunkの仕様 |
sum(eval(...)) |
NG件数のみ ✅ | 正しい方法 |
🔑 トークン設定の仕組み
<done>
<condition match="$result.ng$ > 0">
<set token="box16_color">red</set>
<set token="box16_state">NG</set>
<set token="line_color">red</set>
</condition>
<condition>
<set token="box16_color">green</set>
<set token="box16_state">OK</set>
<set token="line_color">gray</set>
</condition>
</done>
構文解説
| 要素 | 意味 |
|---|---|
<done> |
検索完了時に実行 |
$result.ng$ |
SPLで作った ng の値を参照 |
> |
XML内で > を表す(Greater Than) |
<set token="..."> |
トークン変数に値を格納 |
動作フロー
検索結果: ng = 2
↓
$result.ng$ = 2
↓
2 > 0 ? → YES
↓
box16_color = "red"
box16_state = "NG"
line_color = "red"
Part 2️⃣:HTML でトークンを展開
📝 HTML構造
<div class="flow">
<!-- 固定の緑ボックス -->
<div class="box green">
<div>首都高 湾岸線</div>
<div>OK</div>
</div>
<!-- 動的な線(色が変わる) -->
<div class="line $line_color$"></div>
<!-- 動的なボックス(色と文字が変わる) -->
<div class="box $box16_color$">
<div>国道16号</div>
<div>$box16_state$</div>
</div>
<div class="line $line_color$"></div>
<!-- 固定の緑ボックス -->
<div class="box green">
<div>東名高速</div>
<div>OK</div>
</div>
<div class="line gray"></div>
<div class="box green">
<div>中央高速</div>
<div>OK</div>
</div>
</div>
🔄 トークン展開の仕組み
レンダリング前(ソースコード)
<div class="box $box16_color$">
<div>国道16号</div>
<div>$box16_state$</div>
</div>
レンダリング後(NG時)
<div class="box red">
<div>国道16号</div>
<div>NG</div>
</div>
レンダリング後(OK時)
<div class="box green">
<div>国道16号</div>
<div>OK</div>
</div>
💡 固定と動的の使い分け
| ボックス | 実装方法 | 理由 |
|---|---|---|
| 首都高湾岸線 |
class="box green" 固定 |
今回は判定対象外 |
| 国道16号 |
class="box $box16_color$" 動的 |
検索結果で色が変わる |
| 東名高速 |
class="box green" 固定 |
今回は判定対象外 |
| 中央高速 |
class="box green" 固定 |
今回は判定対象外 |
Part 3️⃣:CSS でスタイルとアニメーション
🎨 CSS全体構造
/* レイアウト */
.flow { ... } /* 横並び配置 */
.box { ... } /* ボックス基本形 */
/* 色定義 */
.green { ... } /* 緑ボックス */
.red { ... } /* 赤ボックス + アニメ */
/* 線 */
.line { ... } /* 線の基本形 */
.line.gray { ... } /* グレーの線 */
.line.red { ... } /* 赤い線 + アニメ */
/* アニメーション */
@keyframes pulse { ... } /* ボックスの脈打ち */
@keyframes blink { ... } /* 線の点滅 */
🔧 レイアウト設計
① 横並び配置(Flexbox)
.flow{
display: flex; /* Flexbox有効化 */
align-items: center; /* 縦方向の中央揃え */
padding: 30px;
}
視覚イメージ:
┌─────────────────────────────────────────┐
│ padding: 30px │
│ ┌────┐ ━━ ┌────┐ ━━ ┌────┐ ━━ ┌────┐ │
│ │BOX1│ │BOX2│ │BOX3│ │BOX4│ │
│ └────┘ └────┘ └────┘ └────┘ │
│ ↑ align-items: center(縦中央揃え) │
└─────────────────────────────────────────┘
② ボックスデザイン
.box{
width: 200px;
height: 110px;
border-radius: 14px; /* 角丸 */
display: flex;
flex-direction: column; /* 内部を縦並び */
align-items: center; /* 横方向の中央揃え */
justify-content: center; /* 縦方向の中央揃え */
font-weight: bold;
color: white;
}
視覚イメージ:
┌──────────────────────┐
│ │
│ ┌─────────┐ │
│ │ 国道16号 │ │ ← align-items: center
│ └─────────┘ │
│ ┌─────┐ │
│ │ NG │ │ ← justify-content: center
│ └─────┘ │
│ │
└──────────────────────┘
↑ 200px ↑
🎭 色とアニメーションの実装
① 緑ボックス(通常状態)
.green {
background: #4CAF50; /* Material Design の緑 */
}
② 赤ボックス(NG状態 + 脈打ちアニメ)
.red {
background: #E53935; /* Material Design の赤 */
animation: pulse 1.5s infinite; /* アニメーション適用 */
}
パラメータ解説:
| 値 | 意味 |
|---|---|
pulse |
アニメーション名(後で定義) |
1.5s |
1周期の長さ(1.5秒) |
infinite |
無限ループ |
③ 脈打ちアニメーション定義
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(229,57,53,0.7);
}
70% {
box-shadow: 0 0 0 15px rgba(229,57,53,0);
}
100% {
box-shadow: 0 0 0 0 rgba(229,57,53,0);
}
}
動作解説:
| タイミング | box-shadow | 視覚効果 |
|---|---|---|
| 0% | 広がり 0px / 不透明度 70% | ボックスに密着 |
| 70% | 広がり 15px / 不透明度 0% | 外側に広がって消える |
| 100% | 広がり 0px / 不透明度 0% | 初期状態に戻る |
視覚イメージ:
時間経過 →
0% 30% 60% 100%
┌───┐ ┌───┐ ┌───┐ ┌───┐
│BOX│ → │BOX│ → │BOX│ → │BOX│
└───┘ └───┘ └───┘ └───┘
■ ▓ ░ (透明)
↑ 影が広がって薄くなる
④ 線のスタイル
.line {
width: 60px;
height: 6px;
margin: 0 10px; /* 左右に余白 */
background: #999;
}
.line.gray {
background: #999; /* グレー(固定) */
}
.line.red {
background: #E53935; /* 赤 */
animation: blink 1s infinite; /* 点滅アニメ */
}
⑤ 点滅アニメーション定義
@keyframes blink {
0%, 100% {
opacity: 1; /* 完全に見える */
}
50% {
opacity: 0.3; /* 薄くなる */
}
}
動作:
時間経過 →
0% 0.5s 1s 1.5s 2s
━━━━ ···· ━━━━ ···· ━━━━
100% 30% 100% 30% 100%
↑ opacity(不透明度)
🎯 クラスの動的切り替えの仕組み
トークンによるクラス名変更
① ボックスのクラス切り替え
ソースコード:
<div class="box $box16_color$">
NG時(box16_color = "red"):
<div class="box red"> <!-- .red のスタイルが適用される -->
OK時(box16_color = "green"):
<div class="box green"> <!-- .green のスタイルが適用される -->
② 線のクラス切り替え
ソースコード:
<div class="line $line_color$"></div>
NG時(line_color = "red"):
<div class="line red"> <!-- 点滅する赤い線 -->
OK時(line_color = "gray"):
<div class="line gray"> <!-- 固定のグレー線 -->
📊 クラスとスタイルの対応表
| トークン値 | HTML結果 | 適用CSS | 視覚効果 |
|---|---|---|---|
box16_color="green" |
class="box green" |
.green{background:#4CAF50;} |
緑色・静止 |
box16_color="red" |
class="box red" |
.red{background:#E53935; animation:pulse...} |
赤色・脈打ち |
line_color="gray" |
class="line gray" |
.line.gray{background:#999;} |
グレー・静止 |
line_color="red" |
class="line red" |
.line.red{background:#E53935; animation:blink...} |
赤色・点滅 |
🔍 デバッグ方法
問題1:色が変わらない
デバッグ手順
Step 1: 検索結果を確認
<search id="ng_search">
<query>
index=20260115tra
| eval route=if(like(location,"%国道16号%"),"国道16号",null())
| where isnotnull(route)
| stats sum(eval(result="NG")) as ng
| table ng <!-- ← 追加 -->
</query>
</search>
確認ポイント:
- ng の値は何か?(0? 1? 2?)
Step 2: トークンを確認
Dashboard の設定画面で「Tokens」タブを見る
box16_color = "red" ← 設定されているか?
box16_state = "NG"
line_color = "red"
Step 3: HTML を確認
ブラウザの開発者ツール(F12)で要素を検証
<!-- 期待値 -->
<div class="box red">
<!-- 実際の値がこうなっていたらトークン未設定 -->
<div class="box $box16_color$">
問題2:アニメーションが動かない
チェックリスト
□ @keyframes が定義されているか?
□ animation プロパティが正しいか?
□ クラス名が正しく適用されているか?
修正例
/* ❌ 間違い */
.red {
animation: pulse 1.5s; /* infinite がない */
}
/* ✅ 正しい */
.red {
animation: pulse 1.5s infinite;
}
🚀 発展課題
レベル1:4路線すべてを動的にする
<!-- 各道路ごとに search を作成 -->
<search id="wangan_search">...</search>
<search id="route16_search">...</search>
<search id="tomei_search">...</search>
<search id="chuo_search">...</search>
レベル2:WARN(黄色)を追加
SPL側
| eval status=case(
result="NG", 2,
result="WARN", 1,
1=1, 0
)
| stats max(status) as level
トークン側
<condition match="$result.level$=2">
<set token="box_color">red</set>
</condition>
<condition match="$result.level$=1">
<set token="box_color">yellow</set>
</condition>
<condition>
<set token="box_color">green</set>
</condition>
CSS側
.yellow {
background: #FFA726;
animation: pulse-yellow 1.5s infinite;
}
@keyframes pulse-yellow {
0% { box-shadow: 0 0 0 0 rgba(255,167,38,0.7); }
70% { box-shadow: 0 0 0 15px rgba(255,167,38,0); }
100% { box-shadow: 0 0 0 0 rgba(255,167,38,0); }
}
レベル3:NG件数を表示
| stats
sum(eval(result="NG")) as ng_count,
count as total
| eval ng_rate=round(ng_count/total*100, 1)
<div class="state">
$box16_state$ ($ng_count$件 / $ng_rate$%)
</div>
📝 まとめ
実装の3本柱
| 技術 | 役割 | ポイント |
|---|---|---|
| SPL | データ判定 |
sum(eval(...)) でNG件数カウント |
| トークン | 動的制御 |
<done> で検索後に色を切り替え |
| CSS | 視覚表現 |
animation で脈打ち・点滅 |
重要なポイント
- JSなしで実装可能 → Splunk Classic Dashboard の標準機能のみ
- トークンが核心 → 検索結果を HTML に橋渡し
- CSS アニメで視認性向上 → 異常を一目で認識可能
完成形コードは記事冒頭を参照! コピペで即動作します 🚀
- 再掲
Splunk Classic Dashboard:絶対位置レイアウト完全パターン集【新人エンジニア向け】
📚 目次
🎯 基礎:最初の四角を作る {#基礎最初の四角を作る}
Step 1:左上に1つの四角
<dashboard version="1.1">
<label>【基礎】左上に四角を1つ作る</label>
<row>
<panel>
<html>
<!-- ========================================== -->
<!-- CSS(スタイル定義) -->
<!-- ========================================== -->
<style>
/* 親コンテナ(基準となる箱) */
.container {
/* 相対位置(これで基準点になる) */
position: relative;
/* コンテナのサイズ */
width: 800px; /* 幅800ピクセル */
height: 600px; /* 高さ600ピクセル */
/* 背景色(薄いグレー) */
background: #f5f5f5;
/* 余白 */
margin: 30px;
/* 枠線(確認用) */
border: 2px solid #ddd;
}
/* 四角ボックス */
.box {
/* 絶対位置(自由に配置できる) */
position: absolute;
/* サイズ */
width: 150px; /* 幅150ピクセル */
height: 100px; /* 高さ100ピクセル */
/* 背景色(青) */
background: #42A5F5;
/* 角を丸くする */
border-radius: 12px;
/* 影をつける */
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
/* 文字色(白) */
color: white;
/* 文字を太字に */
font-weight: bold;
/* 文字を中央揃え */
display: flex; /* Flexboxを使う */
justify-content: center; /* 横方向の中央 */
align-items: center; /* 縦方向の中央 */
}
/* 左上の位置 */
.box1 {
/* 上から20ピクセル */
top: 20px;
/* 左から20ピクセル */
left: 20px;
}
</style>
<!-- ========================================== -->
<!-- HTML(表示する内容) -->
<!-- ========================================== -->
<div class="container">
<!-- 四角ボックス -->
<div class="box box1">
左上の四角
</div>
</div>
</html>
</panel>
</row>
</dashboard>
コードの説明
container (親)
┌─────────────────────────────────┐
│ (0, 0) ← 左上が原点 │
│ │
│ top: 20px │
│ ↓ │
│ ┌──────────┐ │
│ → │ BOX1 │ │
│ └──────────┘ │
│ ↑ │
│ left: 20px │
│ │
└─────────────────────────────────┘
Step 2:四角を増やす(4隅に配置)
<dashboard version="1.1">
<label>【基礎】四隅に四角を配置</label>
<row>
<panel>
<html>
<style>
/* 親コンテナ */
.container {
position: relative;
width: 800px;
height: 600px;
background: #f5f5f5;
margin: 30px;
border: 2px solid #ddd;
}
/* 四角ボックスの共通スタイル */
.box {
position: absolute; /* 絶対位置 */
width: 120px; /* 幅 */
height: 80px; /* 高さ */
border-radius: 10px; /* 角丸 */
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
color: white;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
}
/* ========================================== */
/* 各四角の位置と色 */
/* ========================================== */
/* 左上 */
.box1 {
top: 20px; /* 上から20px */
left: 20px; /* 左から20px */
background: #42A5F5; /* 青 */
}
/* 右上 */
.box2 {
top: 20px; /* 上から20px */
right: 20px; /* 右から20px(rightを使う!) */
background: #4CAF50; /* 緑 */
}
/* 左下 */
.box3 {
bottom: 20px; /* 下から20px(bottomを使う!) */
left: 20px; /* 左から20px */
background: #FFA726; /* 黄色 */
}
/* 右下 */
.box4 {
bottom: 20px; /* 下から20px */
right: 20px; /* 右から20px */
background: #E53935; /* 赤 */
}
</style>
<div class="container">
<!-- 左上 -->
<div class="box box1">左上</div>
<!-- 右上 -->
<div class="box box2">右上</div>
<!-- 左下 -->
<div class="box box3">左下</div>
<!-- 右下 -->
<div class="box box4">右下</div>
</div>
</html>
</panel>
</row>
</dashboard>
🌐 パターン1:ネットワーク構成図 {#パターン1ネットワーク構成図}
完全版:サーバー・スイッチ・クライアント構成
<dashboard version="1.1">
<label>【パターン1】ネットワーク構成図</label>
<row>
<panel>
<html>
<style>
/* ========================================== */
/* コンテナ */
/* ========================================== */
.container {
position: relative;
width: 1000px;
height: 700px;
background: #fafafa;
margin: 30px auto;
border: 2px solid #ddd;
}
/* ========================================== */
/* サーバーボックス(大きめ・四角) */
/* ========================================== */
.server {
position: absolute;
width: 180px;
height: 140px;
background: #1976D2; /* 濃い青 */
border-radius: 8px;
box-shadow: 0 6px 15px rgba(0,0,0,0.3);
color: white;
font-weight: bold;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* ========================================== */
/* スイッチボックス(中くらい・横長) */
/* ========================================== */
.switch {
position: absolute;
width: 200px;
height: 60px;
background: #388E3C; /* 緑 */
border-radius: 6px;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
color: white;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
}
/* ========================================== */
/* クライアントボックス(小さめ・円形っぽく) */
/* ========================================== */
.client {
position: absolute;
width: 100px;
height: 100px;
background: #757575; /* グレー */
border-radius: 50%; /* 円形 */
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
color: white;
font-weight: bold;
font-size: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* ========================================== */
/* 接続線(縦・横) */
/* ========================================== */
.line {
position: absolute;
background: #999;
}
/* 横線 */
.line-h {
height: 3px;
}
/* 縦線 */
.line-v {
width: 3px;
}
/* ========================================== */
/* 各要素の配置 */
/* ========================================== */
/* サーバー1(左上) */
.server1 {
top: 50px;
left: 80px;
}
/* サーバー2(右上) */
.server2 {
top: 50px;
right: 80px;
}
/* スイッチ(中央) */
.switch1 {
top: 280px;
left: 400px; /* 中央に配置 */
}
/* クライアント1(左下) */
.client1 {
bottom: 80px;
left: 100px;
}
/* クライアント2(中央下) */
.client2 {
bottom: 80px;
left: 350px;
}
/* クライアント3(右下) */
.client3 {
bottom: 80px;
right: 100px;
}
/* ========================================== */
/* 接続線の配置 */
/* ========================================== */
/* サーバー1 → スイッチ(縦線) */
.line1 {
top: 200px; /* サーバー1の下端 */
left: 168px; /* サーバー1の中央 */
height: 80px; /* スイッチまでの距離 */
}
/* サーバー2 → スイッチ(縦線) */
.line2 {
top: 200px;
right: 168px; /* サーバー2の中央 */
height: 80px;
}
/* スイッチ → サーバー1(横線) */
.line3 {
top: 308px; /* スイッチの中央 */
left: 268px; /* サーバー1の右端 */
width: 132px; /* スイッチまで */
}
/* スイッチ → サーバー2(横線) */
.line4 {
top: 308px;
left: 600px; /* スイッチの右端 */
width: 152px; /* サーバー2まで */
}
/* スイッチ → クライアント(縦線) */
.line5 {
top: 350px; /* スイッチの下端 */
left: 498px; /* スイッチの中央 */
height: 100px; /* クライアントまで */
}
/* スイッチ → クライアント1(横線) */
.line6 {
top: 450px;
left: 150px;
width: 348px;
}
/* スイッチ → クライアント2(横線) */
.line7 {
top: 450px;
left: 400px;
width: 98px;
}
/* スイッチ → クライアント3(横線) */
.line8 {
top: 450px;
left: 500px;
width: 300px;
}
/* テキストスタイル */
.label {
font-size: 14px;
margin-top: 8px;
}
.sublabel {
font-size: 11px;
margin-top: 4px;
opacity: 0.9;
}
</style>
<!-- ========================================== -->
<!-- HTML構造 -->
<!-- ========================================== -->
<div class="container">
<!-- サーバー1 -->
<div class="server server1">
<div>サーバー1</div>
<div class="sublabel">Web Server</div>
</div>
<!-- サーバー2 -->
<div class="server server2">
<div>サーバー2</div>
<div class="sublabel">DB Server</div>
</div>
<!-- スイッチ -->
<div class="switch switch1">
Core Switch
</div>
<!-- クライアント1 -->
<div class="client client1">
<div>PC-1</div>
<div class="sublabel">192.168.1.10</div>
</div>
<!-- クライアント2 -->
<div class="client client2">
<div>PC-2</div>
<div class="sublabel">192.168.1.11</div>
</div>
<!-- クライアント3 -->
<div class="client client3">
<div>PC-3</div>
<div class="sublabel">192.168.1.12</div>
</div>
<!-- 接続線 -->
<div class="line line-v line1"></div>
<div class="line line-v line2"></div>
<div class="line line-h line3"></div>
<div class="line line-h line4"></div>
<div class="line line-v line5"></div>
<div class="line line-h line6"></div>
<div class="line line-h line7"></div>
<div class="line line-h line8"></div>
</div>
</html>
</panel>
</row>
</dashboard>
🚆 パターン2:山手線風(環状配置) {#パターン2山手線風環状配置}
完全版:駅を円形に配置
<dashboard version="1.1">
<label>【パターン2】山手線風・環状配置</label>
<row>
<panel>
<html>
<style>
/* ========================================== */
/* コンテナ */
/* ========================================== */
.container {
position: relative;
width: 800px;
height: 800px;
background: #fafafa;
margin: 30px auto;
border: 2px solid #ddd;
}
/* ========================================== */
/* 路線(円形の線) */
/* ========================================== */
.rail-circle {
position: absolute;
/* 中央に配置 */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* 円のサイズ */
width: 600px;
height: 600px;
/* 円形の枠線(線路に見立てる) */
border: 8px solid #4CAF50; /* 緑色(山手線) */
border-radius: 50%; /* 円形 */
/* 影 */
box-shadow: 0 0 20px rgba(76, 175, 80, 0.3);
}
/* ========================================== */
/* 駅ボックス */
/* ========================================== */
.station {
position: absolute;
width: 80px;
height: 80px;
background: white;
border: 4px solid #4CAF50;
border-radius: 50%; /* 円形 */
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
/* 文字スタイル */
color: #333;
font-weight: bold;
font-size: 11px;
/* 中央揃え */
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
/* ========================================== */
/* 各駅の配置(12時方向から時計回り) */
/* ========================================== */
/* 東京駅(12時) */
.station1 {
top: 50px;
left: 50%;
transform: translateX(-50%);
}
/* 上野駅(2時) */
.station2 {
top: 130px;
right: 130px;
}
/* 秋葉原駅(3時) */
.station3 {
top: 50%;
right: 50px;
transform: translateY(-50%);
}
/* 品川駅(5時) */
.station4 {
bottom: 130px;
right: 130px;
}
/* 渋谷駅(6時) */
.station5 {
bottom: 50px;
left: 50%;
transform: translateX(-50%);
}
/* 新宿駅(7時) */
.station6 {
bottom: 130px;
left: 130px;
}
/* 池袋駅(9時) */
.station7 {
top: 50%;
left: 50px;
transform: translateY(-50%);
}
/* 上野駅(11時) */
.station8 {
top: 130px;
left: 130px;
}
/* ========================================== */
/* 中央のラベル */
/* ========================================== */
.center-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
font-weight: bold;
color: #4CAF50;
text-align: center;
}
/* ========================================== */
/* ステータス色(Splunkトークンで動的に変更可能) */
/* ========================================== */
.status-ok {
background: #E8F5E9; /* 薄い緑 */
border-color: #4CAF50;
}
.status-ng {
background: #FFEBEE; /* 薄い赤 */
border-color: #E53935;
}
</style>
<!-- ========================================== -->
<!-- HTML構造 -->
<!-- ========================================== -->
<div class="container">
<!-- 路線(円形) -->
<div class="rail-circle"></div>
<!-- 中央ラベル -->
<div class="center-label">
山手線<br/>
<span style="font-size:16px;">JY Line</span>
</div>
<!-- 各駅 -->
<div class="station station1 status-ok">
東京
</div>
<div class="station station2 status-ok">
上野
</div>
<div class="station station3 status-ng">
秋葉原
</div>
<div class="station station4 status-ok">
品川
</div>
<div class="station station5 status-ok">
渋谷
</div>
<div class="station station6 status-ok">
新宿
</div>
<div class="station station7 status-ok">
池袋
</div>
<div class="station station8 status-ok">
日暮里
</div>
</div>
</html>
</panel>
</row>
</dashboard>
Splunk Classic Dashboard:HTML四角ボックス作成 完全ガイド
📚 目次
🎯 基本の1つの四角 {#基本の1つの四角}
Step 1:最小構成のダッシュボード
<dashboard version="1.1">
<!-- ダッシュボードのタイトル -->
<label>四角ボックス:基本編</label>
<!-- 行(row)を作成 -->
<row>
<!-- パネル(panel)を作成 -->
<panel>
<!-- HTMLを書く場所 -->
<html>
<!-- ここにHTMLを書く -->
<div>これは四角です</div>
</html>
</panel>
</row>
</dashboard>
解説(1行ずつ)
| 行 | コード | 意味 |
|---|---|---|
| 1 | <dashboard version="1.1"> |
Splunk Classic Dashboard の宣言 |
| 2 | <label>四角ボックス:基本編</label> |
画面上部に表示されるタイトル |
| 4 | <row> |
横一列のエリアを作る(複数のパネルを横に並べられる) |
| 5 | <panel> |
個別の表示エリア(グラフや表を入れる箱) |
| 6 | <html> |
HTMLを書くための開始タグ |
| 8 | <div>これは四角です</div> |
HTML要素(divタグ)で文字を表示 |
| 10 | </html> |
HTMLの終了タグ |
| 11 | </panel> |
パネルの終了タグ |
| 12 | </row> |
行の終了タグ |
| 13 | </dashboard> |
ダッシュボードの終了タグ |
Step 2:四角に見た目をつける(CSS)
<dashboard version="1.1">
<label>四角ボックス:スタイル付き</label>
<row>
<panel>
<html>
<!-- スタイル(CSS)を定義 -->
<style>
/* .box というクラス名のスタイル */
.box {
/* 幅を200ピクセルに */
width: 200px;
/* 高さを100ピクセルに */
height: 100px;
/* 背景色を青に */
background: #42A5F5;
/* 角を丸くする(14ピクセル) */
border-radius: 14px;
/* 余白を30ピクセル */
padding: 30px;
}
</style>
<!-- 四角を表示(class="box" でスタイルを適用) -->
<div class="box">
これは四角です
</div>
</html>
</panel>
</row>
</dashboard>
解説(CSS部分)
| 行 | コード | 意味 |
|---|---|---|
| 9 | <style> |
CSSを書くための開始タグ |
| 10 | /* コメント */ |
CSSのコメント(説明文) |
| 11 | .box { |
「box」というクラス名のスタイル定義開始 |
| 13 | width: 200px; |
横幅を200ピクセルに設定 |
| 16 | height: 100px; |
縦幅を100ピクセルに設定 |
| 19 | background: #42A5F5; |
背景色を青(#42A5F5)に設定 |
| 22 | border-radius: 14px; |
四隅を丸くする(14ピクセルの半径) |
| 25 | padding: 30px; |
内側の余白を30ピクセル |
| 26 | } |
スタイル定義の終了 |
| 27 | </style> |
CSSの終了タグ |
| 30 | <div class="box"> |
divタグに「box」クラスを適用 |
🎨 四角に色をつける {#四角に色をつける}
Step 3:色を変えられるようにする
<dashboard version="1.1">
<label>四角ボックス:色違い3つ</label>
<row>
<panel>
<html>
<style>
/* 四角の基本スタイル */
.box {
/* 幅 */
width: 200px;
/* 高さ */
height: 100px;
/* 角丸 */
border-radius: 14px;
/* 余白 */
padding: 20px;
/* 文字色を白に */
color: white;
/* 文字を太字に */
font-weight: bold;
/* 文字サイズ */
font-size: 18px;
}
/* 緑色のクラス */
.green {
/* 背景を緑に */
background: #4CAF50;
}
/* 赤色のクラス */
.red {
/* 背景を赤に */
background: #E53935;
}
/* 黄色のクラス */
.yellow {
/* 背景を黄色に */
background: #FFA726;
}
</style>
<!-- 緑の四角 -->
<div class="box green">
緑の四角
</div>
<!-- 赤の四角 -->
<div class="box red">
赤の四角
</div>
<!-- 黄色の四角 -->
<div class="box yellow">
黄色の四角
</div>
</html>
</panel>
</row>
</dashboard>
解説(色の仕組み)
<!-- 2つのクラスを同時に指定 -->
<div class="box green">
意味:
-
boxクラス → 幅・高さ・角丸などの基本スタイル -
greenクラス → 背景色だけ上書き
結果: 緑色の四角ができる
📝 四角の中に文字を入れる {#四角の中に文字を入れる}
Step 4:文字を中央揃えにする
<dashboard version="1.1">
<label>四角ボックス:文字中央揃え</label>
<row>
<panel>
<html>
<style>
.box {
width: 200px;
height: 100px;
background: #42A5F5;
border-radius: 14px;
color: white;
font-weight: bold;
/* Flexboxで中央揃え */
/* Flexboxを有効化 */
display: flex;
/* 横方向の中央揃え */
justify-content: center;
/* 縦方向の中央揃え */
align-items: center;
}
</style>
<!-- 文字が中央に配置される -->
<div class="box">
中央揃え
</div>
</html>
</panel>
</row>
</dashboard>
Flexbox 解説
| プロパティ | 意味 | 値 |
|---|---|---|
display: flex |
Flexboxレイアウトを有効化 | - |
justify-content |
横方向の配置 |
center(中央)、flex-start(左)、flex-end(右) |
align-items |
縦方向の配置 |
center(中央)、flex-start(上)、flex-end(下) |
Step 5:複数行の文字を入れる
<dashboard version="1.1">
<label>四角ボックス:複数行テキスト</label>
<row>
<panel>
<html>
<style>
.box {
width: 200px;
height: 120px;
background: #4CAF50;
border-radius: 14px;
color: white;
/* Flexboxで縦並び */
display: flex;
/* 子要素を縦に並べる */
flex-direction: column;
/* 横方向の中央揃え */
justify-content: center;
/* 縦方向の中央揃え */
align-items: center;
}
/* タイトル部分のスタイル */
.title {
/* フォントサイズ */
font-size: 16px;
/* 下に余白 */
margin-bottom: 8px;
}
/* ステータス部分のスタイル */
.status {
/* フォントサイズを大きく */
font-size: 24px;
/* 太字 */
font-weight: bold;
}
</style>
<div class="box">
<!-- タイトル -->
<div class="title">国道16号</div>
<!-- ステータス -->
<div class="status">OK</div>
</div>
</html>
</panel>
</row>
</dashboard>
HTML構造の解説
<div class="box"> ← 外側の四角
<div class="title">国道16号</div> ← 小さい文字(タイトル)
<div class="status">OK</div> ← 大きい文字(ステータス)
</div>
ポイント:
- 外側の
boxでflex-direction: columnを指定 - 内側の
titleとstatusが縦に並ぶ
🔄 Splunkトークンで動的に色を変える {#splunkトークンで動的に色を変える}
Step 6:トークンで色を切り替え
<dashboard version="1.1">
<label>四角ボックス:動的に色変更</label>
<!-- ダミー検索(実際のデータの代わり) -->
<search id="dummy_search">
<query>
<!-- ダミーデータを作成 -->
| makeresults
<!-- NG判定(1=NG, 0=OK) -->
| eval is_ng=1
</query>
<!-- 検索完了時にトークンを設定 -->
<done>
<!-- is_ng が 1 の場合 -->
<condition match="$result.is_ng$=1">
<!-- box_color トークンに "red" を設定 -->
<set token="box_color">red</set>
<!-- box_state トークンに "NG" を設定 -->
<set token="box_state">NG</set>
</condition>
<!-- それ以外の場合 -->
<condition>
<!-- box_color トークンに "green" を設定 -->
<set token="box_color">green</set>
<!-- box_state トークンに "OK" を設定 -->
<set token="box_state">OK</set>
</condition>
</done>
</search>
<row>
<panel>
<html>
<style>
.box {
width: 200px;
height: 120px;
border-radius: 14px;
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* 緑色 */
.green {
background: #4CAF50;
}
/* 赤色 */
.red {
background: #E53935;
}
.title {
font-size: 16px;
margin-bottom: 8px;
}
.status {
font-size: 24px;
font-weight: bold;
}
</style>
<!-- $box_color$ がトークンの値に置き換わる -->
<div class="box $box_color$">
<div class="title">国道16号</div>
<!-- $box_state$ がトークンの値に置き換わる -->
<div class="status">$box_state$</div>
</div>
</html>
</panel>
</row>
</dashboard>
トークンの仕組み解説
① 検索でトークンを設定:
<set token="box_color">red</set>
→ box_color という変数に red を保存
② HTMLでトークンを使用:
<div class="box $box_color$">
→ $box_color$ が red に置き換わる
③ 最終的なHTML(レンダリング後):
<div class="box red">
📦 完全版:コピペ用テンプレート {#完全版コピペ用テンプレート}
テンプレート:1つの動的四角
<dashboard version="1.1">
<label>【テンプレート】動的四角ボックス</label>
<!-- ========================================== -->
<!-- 検索部分(ここをカスタマイズ) -->
<!-- ========================================== -->
<search id="box1_search">
<query>
<!-- ★ここに実際の検索を書く -->
index=mydata
| where box_id="BOX1"
| sort -_time
| head 1
| eval is_ng=if(status="NG",1,0)
</query>
<!-- 検索完了時の処理 -->
<done>
<!-- NGの場合 -->
<condition match="$result.is_ng$=1">
<set token="box1_color">red</set>
<set token="box1_state">NG</set>
</condition>
<!-- OKの場合 -->
<condition>
<set token="box1_color">green</set>
<set token="box1_state">OK</set>
</condition>
</done>
</search>
<!-- ========================================== -->
<!-- 表示部分(基本的に変更不要) -->
<!-- ========================================== -->
<row>
<panel>
<html>
<!-- スタイル定義 -->
<style>
/* 四角の基本スタイル */
.box {
/* サイズ */
width: 200px;
height: 120px;
/* 見た目 */
border-radius: 14px;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
/* 文字 */
color: white;
font-weight: bold;
/* 中央揃え */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* 色定義 */
.green { background: #4CAF50; }
.red { background: #E53935; }
.yellow { background: #FFA726; }
.blue { background: #42A5F5; }
.gray { background: #757575; }
/* テキストスタイル */
.box-title {
font-size: 16px;
margin-bottom: 8px;
}
.box-status {
font-size: 24px;
font-weight: bold;
}
</style>
<!-- 四角の表示 -->
<div class="box $box1_color$">
<!-- ★ここにタイトルを書く -->
<div class="box-title">国道16号</div>
<!-- ステータス(トークン) -->
<div class="box-status">$box1_state$</div>
</div>
</html>
</panel>
</row>
</dashboard>
カスタマイズポイント
① 検索部分を変更
<query>
<!-- 自分のインデックスとフィールドに変更 -->
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
</query>
② タイトルを変更
<div class="box-title">首都高 湾岸線</div>
③ トークン名を変更(複数ボックスの場合)
<!-- BOX1用 -->
<set token="box1_color">red</set>
<set token="box1_state">NG</set>
<!-- BOX2用 -->
<set token="box2_color">green</set>
<set token="box2_state">OK</set>
④ 色を追加
/* 紫色を追加 */
.purple { background: #9C27B0; }
<div class="box purple">
⑤ サイズを変更
.box {
width: 250px; /* 幅を変更 */
height: 150px; /* 高さを変更 */
}
🎯 横に四角を4つ並べる {#横に四角を4つ並べる}
完全版:4ボックス横並び
<dashboard version="1.1">
<label>4つの四角ボックス(横並び)</label>
<!-- ========================================== -->
<!-- 検索部分(4つ分) -->
<!-- ========================================== -->
<!-- BOX1 検索 -->
<search id="box1_search">
<query>
index=20260115tra
| where like(location,"%湾岸%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box1_color">red</set>
<set token="box1_state">NG</set>
</condition>
<condition>
<set token="box1_color">green</set>
<set token="box1_state">OK</set>
</condition>
</done>
</search>
<!-- BOX2 検索 -->
<search id="box2_search">
<query>
index=20260115tra
| where like(location,"%国道16号%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box2_color">red</set>
<set token="box2_state">NG</set>
</condition>
<condition>
<set token="box2_color">green</set>
<set token="box2_state">OK</set>
</condition>
</done>
</search>
<!-- BOX3 検索 -->
<search id="box3_search">
<query>
index=20260115tra
| where like(location,"%東名%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box3_color">red</set>
<set token="box3_state">NG</set>
</condition>
<condition>
<set token="box3_color">green</set>
<set token="box3_state">OK</set>
</condition>
</done>
</search>
<!-- BOX4 検索 -->
<search id="box4_search">
<query>
index=20260115tra
| where like(location,"%中央高速%")
| sort -_time
| head 1
| eval is_ng=if(result="NG",1,0)
</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box4_color">red</set>
<set token="box4_state">NG</set>
</condition>
<condition>
<set token="box4_color">green</set>
<set token="box4_state">OK</set>
</condition>
</done>
</search>
<!-- ========================================== -->
<!-- 表示部分 -->
<!-- ========================================== -->
<row>
<panel>
<html>
<style>
/* ========================================== */
/* 横並びコンテナ */
/* ========================================== */
.container {
/* Flexboxで横並び */
display: flex;
/* 子要素を横方向に配置 */
flex-direction: row;
/* 縦方向の中央揃え */
align-items: center;
/* 要素間の余白 */
gap: 30px;
/* 外側の余白 */
padding: 30px;
}
/* ========================================== */
/* 四角ボックス */
/* ========================================== */
.box {
/* サイズ */
width: 200px;
height: 120px;
/* 見た目 */
border-radius: 14px;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
/* 文字 */
color: white;
font-weight: bold;
/* 中央揃え */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* ========================================== */
/* 色定義 */
/* ========================================== */
.green {
/* 緑色 */
background: #4CAF50;
}
.red {
/* 赤色 */
background: #E53935;
}
.yellow {
/* 黄色 */
background: #FFA726;
}
.blue {
/* 青色 */
background: #42A5F5;
}
.gray {
/* グレー */
background: #757575;
}
/* ========================================== */
/* テキストスタイル */
/* ========================================== */
.box-title {
/* タイトルのサイズ */
font-size: 15px;
/* 下に余白 */
margin-bottom: 8px;
}
.box-status {
/* ステータスのサイズ */
font-size: 22px;
/* 太字 */
font-weight: bold;
}
</style>
<!-- ========================================== -->
<!-- 横並びコンテナ -->
<!-- ========================================== -->
<div class="container">
<!-- BOX1:首都高 湾岸線 -->
<div class="box $box1_color$">
<div class="box-title">首都高 湾岸線</div>
<div class="box-status">$box1_state$</div>
</div>
<!-- BOX2:国道16号 -->
<div class="box $box2_color$">
<div class="box-title">国道16号</div>
<div class="box-status">$box2_state$</div>
</div>
<!-- BOX3:東名高速 -->
<div class="box $box3_color$">
<div class="box-title">東名高速</div>
<div class="box-status">$box3_state$</div>
</div>
<!-- BOX4:中央高速 -->
<div class="box $box4_color$">
<div class="box-title">中央高速</div>
<div class="box-status">$box4_state$</div>
</div>
</div>
</html>
</panel>
</row>
</dashboard>
横並びの仕組み解説
① コンテナで囲む
<div class="container">
<!-- この中に四角を入れる -->
<div class="box">BOX1</div>
<div class="box">BOX2</div>
<div class="box">BOX3</div>
<div class="box">BOX4</div>
</div>
② Flexboxで横並び
.container {
/* Flexboxを有効化 */
display: flex;
/* 横方向に配置(これが重要!) */
flex-direction: row;
/* 要素間の余白 */
gap: 30px;
}
ポイント:
-
display: flex→ Flexbox有効化 -
flex-direction: row→ 横並び(デフォルト値だが明示的に書く) -
gap: 30px→ ボックス間の余白
応用パターン
パターン1:等間隔に配置
.container {
display: flex;
/* 両端を揃えて、間を均等に */
justify-content: space-between;
}
パターン2:中央揃え
.container {
display: flex;
/* 中央に寄せる */
justify-content: center;
gap: 20px;
}
パターン3:右寄せ
.container {
display: flex;
/* 右に寄せる */
justify-content: flex-end;
gap: 20px;
}
パターン4:折り返し(5個以上の場合)
.container {
display: flex;
/* 折り返しを許可 */
flex-wrap: wrap;
gap: 20px;
}
カスタマイズ例
① ボックス間に線を入れる
<div class="container">
<div class="box $box1_color$">...</div>
<!-- 線 -->
<div style="width:2px; height:80px; background:#999;"></div>
<div class="box $box2_color$">...</div>
<div style="width:2px; height:80px; background:#999;"></div>
<div class="box $box3_color$">...</div>
</div>
② ボックスのサイズを個別に変える
/* BOX1だけ大きくする */
.box.large {
width: 250px;
height: 150px;
}
<div class="box large $box1_color$">...</div>
③ ホバー効果を追加
.box:hover {
/* マウスを乗せたら少し大きくする */
transform: scale(1.05);
/* スムーズに変化 */
transition: transform 0.3s;
}
📝 まとめ
基本構造
Dashboard
└ Row(行)
└ Panel(パネル)
└ HTML
├ <style> (CSS)
└ <div> (HTML要素)
重要なポイント
| 項目 | 説明 |
|---|---|
display: flex |
横並び・中央揃えの基本 |
class="box $token$" |
トークンで動的にクラスを変更 |
gap: 30px |
ボックス間の余白 |
border-radius |
角を丸くする |
このテンプレートをコピペして、自分のダッシュボードを作ろう! 🚀
Splunk Dashboard:絶対位置で複数の四角を自由配置する完全ガイド
📚 目次
🎯 絶対位置の基本 {#絶対位置の基本}
Step 1:position の種類
| position | 意味 | 用途 |
|---|---|---|
static |
デフォルト(通常の配置) | 特に指定しない場合 |
relative |
相対位置(元の位置を基準) | 親要素として使う |
absolute |
絶対位置(親要素を基準) | 自由配置に使う |
fixed |
固定位置(画面を基準) | スクロールしても動かない |
Step 2:最小構成のコード
<dashboard version="1.1">
<label>絶対位置:基本編</label>
<row>
<panel>
<html>
<style>
/* ========================================== */
/* 親コンテナ(基準となる箱) */
/* ========================================== */
.container {
/* 相対位置(これが重要!) */
position: relative;
/* コンテナのサイズを指定 */
width: 800px;
height: 600px;
/* 背景色(確認用) */
background: #f5f5f5;
/* 余白 */
margin: 30px;
}
/* ========================================== */
/* 四角(絶対位置で配置) */
/* ========================================== */
.box {
/* 絶対位置(これで自由配置可能) */
position: absolute;
/* サイズ */
width: 150px;
height: 100px;
/* 見た目 */
background: #42A5F5;
border-radius: 12px;
color: white;
/* 文字を中央に */
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* ========================================== */
/* BOX1 の位置 */
/* ========================================== */
.box1 {
/* 上から50ピクセル */
top: 50px;
/* 左から50ピクセル */
left: 50px;
}
/* ========================================== */
/* BOX2 の位置 */
/* ========================================== */
.box2 {
/* 上から50ピクセル */
top: 50px;
/* 左から300ピクセル */
left: 300px;
/* 色を変える */
background: #4CAF50;
}
</style>
<!-- ========================================== -->
<!-- HTML構造 -->
<!-- ========================================== -->
<!-- 親コンテナ -->
<div class="container">
<!-- BOX1 -->
<div class="box box1">
BOX1
</div>
<!-- BOX2 -->
<div class="box box2">
BOX2
</div>
</div>
</html>
</panel>
</row>
</dashboard>
動作の仕組み
① 親要素を position: relative にする
.container {
position: relative; /* これで基準点になる */
width: 800px;
height: 600px;
}
意味: この箱の左上が座標 (0, 0) になる
② 子要素を position: absolute にする
.box {
position: absolute; /* 絶対位置で配置 */
}
意味: 親要素を基準に自由に配置できる
③ top と left で位置を指定
.box1 {
top: 50px; /* 上から50ピクセル */
left: 50px; /* 左から50ピクセル */
}
📐 座標系の理解 {#座標系の理解}
座標の指定方法
┌─────────────────────────────────┐
│ container (800px × 600px) │
│ (0, 0) ← 左上が原点 │
│ │
│ top: 50px │
│ ↓ │
│ ┌────────┐ │
│ → │ BOX1 │ │
│ └────────┘ │
│ ↑ │
│ left: 50px │
│ │
└─────────────────────────────────┘
4つの位置指定プロパティ
| プロパティ | 意味 | 例 |
|---|---|---|
top |
上からの距離 | top: 50px |
bottom |
下からの距離 | bottom: 50px |
left |
左からの距離 | left: 50px |
right |
右からの距離 | right: 50px |
位置指定のパターン
パターン1:左上
.box {
top: 0;
left: 0;
}
パターン2:右上
.box {
top: 0;
right: 0;
}
パターン3:左下
.box {
bottom: 0;
left: 0;
}
パターン4:右下
.box {
bottom: 0;
right: 0;
}
パターン5:中央
.box {
/* 左から50%の位置 */
left: 50%;
/* 上から50%の位置 */
top: 50%;
/* 自分のサイズの半分だけ戻す */
transform: translate(-50%, -50%);
}
🎨 基本パターン:2つの四角 {#基本パターン2つの四角}
パターン1:横並び(絶対位置版)
<dashboard version="1.1">
<label>絶対位置:横並び</label>
<row>
<panel>
<html>
<style>
.container {
position: relative;
width: 600px;
height: 200px;
background: #f5f5f5;
margin: 30px;
}
.box {
position: absolute;
width: 150px;
height: 100px;
background: #42A5F5;
border-radius: 12px;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* BOX1:左側 */
.box1 {
top: 50px; /* 上から50px */
left: 50px; /* 左から50px */
}
/* BOX2:右側 */
.box2 {
top: 50px; /* 上から50px */
left: 300px; /* 左から300px */
background: #4CAF50;
}
</style>
<div class="container">
<div class="box box1">BOX1</div>
<div class="box box2">BOX2</div>
</div>
</html>
</panel>
</row>
</dashboard>
パターン2:縦並び
<dashboard version="1.1">
<label>絶対位置:縦並び</label>
<row>
<panel>
<html>
<style>
.container {
position: relative;
width: 300px;
height: 400px;
background: #f5f5f5;
margin: 30px;
}
.box {
position: absolute;
width: 200px;
height: 80px;
background: #42A5F5;
border-radius: 12px;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* BOX1:上 */
.box1 {
top: 50px; /* 上から50px */
left: 50px; /* 左から50px */
}
/* BOX2:下 */
.box2 {
top: 200px; /* 上から200px */
left: 50px; /* 左から50px */
background: #4CAF50;
}
</style>
<div class="container">
<div class="box box1">BOX1</div>
<div class="box box2">BOX2</div>
</div>
</html>
</panel>
</row>
</dashboard>
パターン3:斜め配置
<dashboard version="1.1">
<label>絶対位置:斜め配置</label>
<row>
<panel>
<html>
<style>
.container {
position: relative;
width: 600px;
height: 400px;
background: #f5f5f5;
margin: 30px;
}
.box {
position: absolute;
width: 150px;
height: 100px;
background: #42A5F5;
border-radius: 12px;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* BOX1:左上 */
.box1 {
top: 50px;
left: 50px;
}
/* BOX2:右下 */
.box2 {
top: 250px;
left: 400px;
background: #4CAF50;
}
</style>
<div class="container">
<div class="box box1">BOX1</div>
<div class="box box2">BOX2</div>
</div>
</html>
</panel>
</row>
</dashboard>
🔀 応用パターン:複数配置 {#応用パターン複数配置}
パターン4:4つの四角(四隅)
<dashboard version="1.1">
<label>絶対位置:四隅配置</label>
<row>
<panel>
<html>
<style>
.container {
position: relative;
width: 600px;
height: 400px;
background: #f5f5f5;
margin: 30px;
}
.box {
position: absolute;
width: 120px;
height: 80px;
border-radius: 10px;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* ========================================== */
/* 位置指定 */
/* ========================================== */
/* 左上 */
.box1 {
top: 20px;
left: 20px;
background: #42A5F5;
}
/* 右上 */
.box2 {
top: 20px;
right: 20px;
background: #4CAF50;
}
/* 左下 */
.box3 {
bottom: 20px;
left: 20px;
background: #FFA726;
}
/* 右下 */
.box4 {
bottom: 20px;
right: 20px;
background: #E53935;
}
</style>
<div class="container">
<div class="box box1">BOX1</div>
<div class="box box2">BOX2</div>
<div class="box box3">BOX3</div>
<div class="box box4">BOX4</div>
</div>
</html>
</panel>
</row>
</dashboard>
パターン5:グリッド状配置(3x3)
<dashboard version="1.1">
<label>絶対位置:グリッド配置</label>
<row>
<panel>
<html>
<style>
.container {
position: relative;
width: 700px;
height: 500px;
background: #f5f5f5;
margin: 30px;
}
.box {
position: absolute;
width: 100px;
height: 80px;
border-radius: 10px;
color: white;
background: #42A5F5;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* ========================================== */
/* 3×3 グリッド配置 */
/* ========================================== */
/* 1行目 */
.box1 { top: 50px; left: 50px; }
.box2 { top: 50px; left: 250px; }
.box3 { top: 50px; left: 450px; }
/* 2行目 */
.box4 { top: 200px; left: 50px; background: #4CAF50; }
.box5 { top: 200px; left: 250px; background: #4CAF50; }
.box6 { top: 200px; left: 450px; background: #4CAF50; }
/* 3行目 */
.box7 { top: 350px; left: 50px; background: #FFA726; }
.box8 { top: 350px; left: 250px; background: #FFA726; }
.box9 { top: 350px; left: 450px; background: #FFA726; }
</style>
<div class="container">
<div class="box box1">1</div>
<div class="box box2">2</div>
<div class="box box3">3</div>
<div class="box box4">4</div>
<div class="box box5">5</div>
<div class="box box6">6</div>
<div class="box box7">7</div>
<div class="box box8">8</div>
<div class="box box9">9</div>
</div>
</html>
</panel>
</row>
</dashboard>
パターン6:円形配置
<dashboard version="1.1">
<label>絶対位置:円形配置</label>
<row>
<panel>
<html>
<style>
.container {
position: relative;
width: 600px;
height: 600px;
background: #f5f5f5;
margin: 30px;
}
.box {
position: absolute;
width: 80px;
height: 80px;
border-radius: 50%; /* 円形に */
color: white;
background: #42A5F5;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
/* 中央 */
.center {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100px;
height: 100px;
background: #E53935;
}
/* ========================================== */
/* 円形配置(12時、3時、6時、9時) */
/* ========================================== */
/* 12時(上) */
.box1 {
top: 50px;
left: 50%;
transform: translateX(-50%);
}
/* 3時(右) */
.box2 {
top: 50%;
right: 50px;
transform: translateY(-50%);
background: #4CAF50;
}
/* 6時(下) */
.box3 {
bottom: 50px;
left: 50%;
transform: translateX(-50%);
background: #FFA726;
}
/* 9時(左) */
.box4 {
top: 50%;
left: 50px;
transform: translateY(-50%);
background: #9C27B0;
}
</style>
<div class="container">
<!-- 中央 -->
<div class="box center">中央</div>
<!-- 周辺4つ -->
<div class="box box1">12時</div>
<div class="box box2">3時</div>
<div class="box box3">6時</div>
<div class="box box4">9時</div>
</div>
</html>
</panel>
</row>
</dashboard>
🎯 実践例:フロー図 {#実践例フロー図}
完全版:プロセスフロー
<dashboard version="1.1">
<label>絶対位置:プロセスフロー図</label>
<!-- ========================================== -->
<!-- 検索部分(トークン設定) -->
<!-- ========================================== -->
<search id="box1_search">
<query>| makeresults | eval is_ng=0</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box1_color">red</set>
</condition>
<condition>
<set token="box1_color">green</set>
</condition>
</done>
</search>
<search id="box2_search">
<query>| makeresults | eval is_ng=1</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box2_color">red</set>
</condition>
<condition>
<set token="box2_color">green</set>
</condition>
</done>
</search>
<search id="box3_search">
<query>| makeresults | eval is_ng=0</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box3_color">red</set>
</condition>
<condition>
<set token="box3_color">green</set>
</condition>
</done>
</search>
<search id="box4_search">
<query>| makeresults | eval is_ng=0</query>
<done>
<condition match="$result.is_ng$=1">
<set token="box4_color">red</set>
</condition>
<condition>
<set token="box4_color">green</set>
</condition>
</done>
</search>
<!-- ========================================== -->
<!-- 表示部分 -->
<!-- ========================================== -->
<row>
<panel>
<html>
<style>
/* ========================================== */
/* コンテナ */
/* ========================================== */
.container {
position: relative;
width: 900px;
height: 500px;
background: #fafafa;
margin: 30px auto;
border: 2px solid #ddd;
}
/* ========================================== */
/* ボックス基本スタイル */
/* ========================================== */
.box {
position: absolute;
width: 150px;
height: 100px;
border-radius: 14px;
color: white;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-weight: bold;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
/* 色定義 */
.green { background: #4CAF50; }
.red { background: #E53935; }
/* テキスト */
.box-title {
font-size: 14px;
margin-bottom: 6px;
}
.box-status {
font-size: 20px;
}
/* ========================================== */
/* 線(矢印) */
/* ========================================== */
.line {
position: absolute;
background: #999;
}
/* 横線 */
.line-h {
height: 4px;
}
/* 縦線 */
.line-v {
width: 4px;
}
/* 矢印 */
.arrow {
position: absolute;
width: 0;
height: 0;
border-style: solid;
}
/* 右向き矢印 */
.arrow-right {
border-width: 8px 0 8px 12px;
border-color: transparent transparent transparent #999;
}
/* 下向き矢印 */
.arrow-down {
border-width: 12px 8px 0 8px;
border-color: #999 transparent transparent transparent;
}
/* ========================================== */
/* 各要素の位置 */
/* ========================================== */
/* BOX1:左上 */
.box1 {
top: 50px;
left: 50px;
}
/* 横線1 */
.line1 {
top: 97px; /* BOX1の中央(50 + 100/2 - 4/2) */
left: 215px; /* BOX1の右端(50 + 150 + 15) */
width: 100px;
}
/* 矢印1 */
.arrow1 {
top: 89px; /* 線の中央(97 - 8) */
left: 315px; /* 線の右端 */
}
/* BOX2:右上 */
.box2 {
top: 50px;
left: 350px;
}
/* 縦線1 */
.line2 {
top: 165px; /* BOX2の下端(50 + 100 + 15) */
left: 422px; /* BOX2の中央(350 + 150/2 - 4/2) */
height: 80px;
}
/* 矢印2 */
.arrow2 {
top: 245px; /* 線の下端 */
left: 414px; /* 線の中央(422 - 8) */
}
/* BOX3:右下 */
.box3 {
top: 280px;
left: 350px;
}
/* 横線2 */
.line3 {
top: 327px; /* BOX3の中央 */
left: 235px; /* BOX3の左端 - 100 */
width: 100px;
}
/* 矢印3(左向き) */
.arrow3 {
top: 319px;
left: 223px;
border-width: 8px 12px 8px 0;
border-color: transparent #999 transparent transparent;
}
/* BOX4:左下 */
.box4 {
top: 280px;
left: 50px;
}
</style>
<!-- ========================================== -->
<!-- HTML構造 -->
<!-- ========================================== -->
<div class="container">
<!-- BOX1:スタート -->
<div class="box box1 $box1_color$">
<div class="box-title">スタート</div>
<div class="box-status">OK</div>
</div>
<!-- 横線1(BOX1→BOX2) -->
<div class="line line-h line1"></div>
<div class="arrow arrow-right arrow1"></div>
<!-- BOX2:処理A -->
<div class="box box2 $box2_color$">
<div class="box-title">処理A</div>
<div class="box-status">NG</div>
</div>
<!-- 縦線1(BOX2→BOX3) -->
<div class="line line-v line2"></div>
<div class="arrow arrow-down arrow2"></div>
<!-- BOX3:処理B -->
<div class="box box3 $box3_color$">
<div class="box-title">処理B</div>
<div class="box-status">OK</div>
</div>
<!-- 横線2(BOX3→BOX4) -->
<div class="line line-h line3"></div>
<div class="arrow arrow3"></div>
<!-- BOX4:終了 -->
<div class="box box4 $box4_color$">
<div class="box-title">終了</div>
<div class="box-status">OK</div>
</div>
</div>
</html>
</panel>
</row>
</dashboard>
📦 完全版テンプレート {#完全版テンプレート}
コピペ用:自由配置テンプレート
<dashboard version="1.1">
<label>【テンプレート】絶対位置・自由配置</label>
<row>
<panel>
<html>
<style>
/* ========================================== */
/* コンテナ(サイズを調整) */
/* ========================================== */
.container {
/* 相対位置(必須) */
position: relative;
/* ★サイズを調整 */
width: 1000px;
height: 600px;
/* 背景色 */
background: #fafafa;
/* 余白 */
margin: 30px auto;
/* 枠線(確認用) */
border: 1px dashed #ccc;
}
/* ========================================== */
/* ボックス共通スタイル */
/* ========================================== */
.box {
/* 絶対位置(必須) */
position: absolute;
/* デフォルトサイズ */
width: 150px;
height: 100px;
/* 見た目 */
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
/* 文字 */
color: white;
font-weight: bold;
/* 中央揃え */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* ========================================== */
/* 色定義 */
/* ========================================== */
.green { background: #4CAF50; }
.red { background: #E53935; }
.yellow { background: #FFA726; }
.blue { background: #42A5F5; }
.purple { background: #9C27B0; }
.gray { background: #757575; }
/* ========================================== */
/* 各ボックスの位置(★ここを調整) */
/* ========================================== */
.box1 {
top: 50px;
left: 50px;
}
.box2 {
top: 50px;
left: 300px;
}
.box3 {
top: 50px;
left: 550px;
}
.box4 {
top: 250px;
left: 50px;
}
.box5 {
top: 250px;
left: 300px;
}
.box6 {
top: 250px;
left: 550px;
}
/* ========================================== */
/* 線(オプション) */
/* ========================================== */
.line {
position: absolute;
background: #999;
}
/* 横線のサンプル */
.line-h-1 {
top: 97px; /* ★調整 */
left: 215px; /* ★調整 */
width: 70px; /* ★調整 */
height:4px;
}
/* 縦線のサンプル */
.line-v-1 {
top: 165px; /* ★調整 */
left: 122px; /* ★調整 */
width: 4px;
height: 70px; /* ★調整 */
}
</style>
<!-- ========================================== -->
<!-- HTML(★ボックスを追加・削除) -->
<!-- ========================================== -->
<div class="container">
<!-- BOX1 -->
<div class="box box1 green">
<div>BOX1</div>
</div>
<!-- BOX2 -->
<div class="box box2 blue">
<div>BOX2</div>
</div>
<!-- BOX3 -->
<div class="box box3 red">
<div>BOX3</div>
</div>
<!-- BOX4 -->
<div class="box box4 yellow">
<div>BOX4</div>
</div>
<!-- BOX5 -->
<div class="box box5 purple">
<div>BOX5</div>
</div>
<!-- BOX6 -->
<div class="box box6 gray">
<div>BOX6</div>
</div>
<!-- 線(オプション) -->
<!-- <div class="line line-h-1"></div> -->
<!-- <div class="line line-v-1"></div> -->
</div>
</html>
</panel>
</row>
</dashboard>
🎓 位置計算のコツ
計算式
横線の中央位置
top = ボックスのtop + (ボックスの高さ / 2) - (線の高さ / 2)
例:
.box1 { top: 50px; height: 100px; }
.line { height: 4px; }
/* 計算 */
top = 50 + (100 / 2) - (4 / 2)
= 50 + 50 - 2
= 98px
縦線の中央位置
left = ボックスのleft + (ボックスの幅 / 2) - (線の幅 / 2)
例:
.box1 { left: 50px; width: 150px; }
.line { width: 4px; }
/* 計算 */
left = 50 + (150 / 2) - (4 / 2)
= 50 + 75 - 2
= 123px
デバッグのコツ
① グリッド背景を追加
.container {
background-image:
linear-gradient(#ddd 1px, transparent 1px),
linear-gradient(90deg, #ddd 1px, transparent 1px);
background-size: 50px 50px;
}
効果: 50pxごとにグリッド線が表示される
② 座標を表示
<div class="box box1 green">
<div>BOX1</div>
<div style="font-size:10px; margin-top:5px;">
(50, 50)
</div>
</div>
📝 まとめ
絶対位置の3ステップ
| Step | 内容 | コード |
|---|---|---|
| 1 | 親を相対位置に | .container { position: relative; } |
| 2 | 子を絶対位置に | .box { position: absolute; } |
| 3 | 座標を指定 | top: 50px; left: 100px; |
位置指定のパターン
| 目的 | プロパティ |
|---|---|
| 左上からの距離 |
top, left
|
| 右下からの距離 |
bottom, right
|
| 中央配置 | left: 50%; transform: translateX(-50%); |
このテンプレートで自由なレイアウトを作ろう! 🎨
お疲れ様でした!