샘플 SQL 쿼리

이 문서에는 로그 애널리틱스를 사용하도록 업그레이드되는 로그 버킷에 저장된 로그 항목에 대한 샘플 쿼리가 포함되어 있습니다. 이러한 버킷에서는 Google Cloud 콘솔의 로그 애널리틱스 페이지에서 SQL 쿼리를 실행할 수 있습니다. 더 많은 샘플은 logging-analytics-samplessecurity-analytics GitHub 저장소를 참조하세요.

이 문서에서는 SQL 또는 로그 항목을 라우팅하고 저장하는 방법에 대해 설명하지 않습니다. 이 주제에 대한 자세한 내용은 다음 단계 섹션을 참조하세요.

시작하기 전에

  • 이 문서에 나온 쿼리를 로그 분석 페이지에서 사용하려면 TABLE을 쿼리하려는 뷰에 해당하는 테이블 이름으로 바꿉니다. 테이블 이름은 project_ID.region.bucket_ID.view_ID 형식입니다. 로그 분석 페이지에서 뷰의 테이블 이름을 찾을 수 있습니다. 로그 뷰의 기본 쿼리는 FROM 문에 테이블 이름을 나열합니다. 기본 쿼리에 액세스하는 방법에 대한 자세한 내용은 로그 뷰 쿼리를 참조하세요.

  • BigQuery BigQuery Studio 페이지에서 이 문서에 표시된 쿼리를 사용하려면 TABLE을 연결된 데이터 세트의 테이블 경로로 바꿉니다. 예를 들어 myproject 프로젝트에 있는 연결된 데이터 세트 mydataset_AllLogs 뷰를 쿼리하려면 이 필드를 myproject.mydataset._AllLogs으로 설정합니다.

    Google Cloud console에서 BigQuery 페이지로 이동합니다.

    BigQuery Studio로 이동

    검색창을 사용하여 이 페이지를 찾을 수도 있습니다.

  • 로그 애널리틱스 페이지를 열려면 다음을 수행합니다.

    1. Google Cloud 콘솔에서 로그 분석 페이지로 이동합니다.

      로그 애널리틱스로 이동

      검색창을 사용하여 이 페이지를 찾은 경우 부제목이 Logging인 결과를 선택합니다.

    2. 선택사항: 로그 뷰의 테이블 스키마를 식별하려면 로그 뷰 목록에서 뷰를 찾은 후 뷰 이름을 선택합니다.

    테이블의 스키마가 표시됩니다. 필터 필드를 사용해서 특정 필드를 찾습니다. 스키마를 수정할 수 없습니다.

로그 필터링

SQL 쿼리는 처리할 테이블의 행을 결정한 후 행을 그룹화하고 집계 작업을 수행합니다. 그룹화 및 집계 작업이 나열되지 않으면 쿼리 결과에 필터 작업으로 선택한 행이 포함됩니다. 이 섹션의 샘플은 필터링에 대해 설명합니다.

시간별 필터링

쿼리 기간을 설정하려면 기간 선택기를 사용하는 것이 좋습니다. 이 선택기는 쿼리가 WHERE 절에 timestamp 필드를 지정하지 않는 경우에 자동으로 사용됩니다. 예를 들어 이전 주의 데이터를 보려면 기간 선택기에서 지난 7일을 선택합니다. 또한 기간 선택기를 사용하여 시작 및 종료 시간을 지정하고, 둘러볼 시간을 지정하고, 시간대를 변경할 수 있습니다.

WHERE 절에 timestamp 필드를 포함하면 시간 범위 선택기 설정은 사용되지 않습니다. 다음 예시에서는 TIMESTAMP_SUB 함수를 사용하여 데이터를 필터링하고 현재 시간에서 뒤돌아보는 간격을 지정할 수 있습니다.

WHERE
  timestamp > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 HOUR)

시간별로 필터링하는 방법에 대한 자세한 내용은 시간 함수타임스탬프 함수를 참조하세요.

리소스별 필터링

리소스별로 필터링하려면 resource.type 제한을 추가합니다.

예를 들어 다음 쿼리는 최근 1시간 동안의 데이터를 읽은 후 리소스 유형이 gce_instance와 일치하는 행을 보관하고 최대 100개의 항목을 정렬하여 표시합니다.

SELECT
  timestamp, log_name, severity, json_payload, resource, labels
FROM
  `TABLE`
WHERE
  resource.type = "gce_instance"
ORDER BY timestamp ASC
LIMIT 100

심각도별 필터링

severity = 'ERROR'와 같은 제한을 가진 특정 심각도를 기준으로 필터링할 수 있습니다. 또 다른 옵션은 IN 문을 사용하고 유효한 값 집합을 지정하는 것입니다.

예를 들어 다음 쿼리는 최근 1시간 동안의 데이터를 읽은 후 값이 'INFO' 또는 'ERROR'severity 필드가 포함된 행만 보관합니다.

SELECT
  timestamp, log_name, severity, json_payload, resource, labels
FROM
  `TABLE`
WHERE
  severity IS NOT NULL AND
  severity IN ('INFO', 'ERROR')
ORDER BY timestamp ASC
LIMIT 100

이전 쿼리는 severity 필드의 값을 기준으로 필터링합니다. 하지만 로그 심각도의 숫자 값으로 필터링하는 쿼리를 작성할 수도 있습니다. 예를 들어 severity 선을 다음 선으로 바꾸면 쿼리는 심각도 수준이 적어도 NOTICE인 모든 로그 항목을 반환합니다.

  severity_number IS NOT NULL AND
  severity_number > 200

열거된 값에 대한 자세한 내용은 LogSeverity를 참조하세요.

로그 이름으로 필터링

로그 이름으로 필터링하려면 log_name 또는 log_id 필드의 값에 대한 제한을 추가할 수 있습니다. log_name 필드에는 리소스 경로가 포함됩니다. 즉, 이 필드에는 projects/myproject/logs/mylog와 같은 값이 포함됩니다. log_id 필드는 mylog와 같은 로그 이름만 저장합니다.

예를 들어 다음 쿼리는 최근 1시간 동안의 데이터를 읽은 후 log_id 필드의 값이 cloudaudit.googleapis.com/data_access인 행을 보관한 다음 결과를 정렬하여 표시합니다.

SELECT
  timestamp, log_id, severity, json_payload, resource, labels
FROM
  `TABLE`
WHERE
  log_id = "cloudaudit.googleapis.com/data_access"
ORDER BY timestamp ASC
LIMIT 100

리소스 라벨로 필터링

대부분의 모니터링 리소스 설명자는 특정 리소스를 식별하는 데 사용되는 라벨을 정의합니다. 예를 들어 Compute Engine 인스턴스의 설명자에는 영역, 프로젝트 ID 및 인스턴스 ID에 대한 라벨이 포함됩니다. 로그 항목이 기록되면 각 필드에 값이 할당됩니다. 다음은 그러한 예시입니다.

{
   type: "gce_instance"
   labels: {
      instance_id: "1234512345123451"
      project_id: "my-project"
      zone: "us-central1-f"
   }
}

labels 필드의 데이터 유형이 JSON이므로 쿼리에 resource.labels.zone = "us-centra1-f"와 같은 제한이 포함되면 구문 오류가 발생합니다. JSON 데이터 유형이 있는 필드 값을 가져오려면 JSON_VALUE 함수를 사용합니다.

예를 들어 다음 쿼리는 최신 데이터를 읽은 후 리소스가 us-central1-f 영역에 있는 Compute Engine 인스턴스인 해당 행을 보관합니다.

SELECT
  timestamp, log_name, severity, JSON_VALUE(resource.labels.zone) AS zone, json_payload, resource, labels
FROM
  `TABLE`
WHERE
  resource.type = "gce_instance" AND
  JSON_VALUE(resource.labels.zone) = "us-central1-f"
ORDER BY timestamp ASC
LIMIT 100

JSON 데이터를 검색하고 변환할 수 있는 모든 함수에 대한 자세한 내용은 JSON 함수를 참조하세요.

HTTP 요청으로 필터링

HTTP 요청 또는 응답에 해당하는 행만 포함되도록 테이블을 필터링하려면 http_request IS NOT NULL 제한을 추가합니다.

SELECT
  timestamp, log_name, severity, http_request, resource, labels
FROM
  `TABLE`
WHERE
  http_request IS NOT NULL
ORDER BY timestamp
LIMIT 100

다음 쿼리에는 GET 또는 POST 요청에 해당하는 행만 포함됩니다.

SELECT
  timestamp, log_name, severity, http_request, resource, labels
FROM
  `TABLE`
WHERE
  http_request IS NOT NULL AND
  http_request.request_method IN ('GET', 'POST')
ORDER BY timestamp ASC
LIMIT 100

HTTP 상태로 필터링

HTTP 상태로 필터링하려면 http_request.status 필드가 정의되도록 WHERE 절을 수정합니다.

SELECT
  timestamp, log_name, http_request.status, http_request, resource, labels
FROM
  `TABLE`
WHERE
  http_request IS NOT NULL AND
  http_request.status IS NOT NULL
ORDER BY timestamp ASC
LIMIT 100

필드에 저장된 데이터 유형을 확인하려면 스키마를 보거나 필드를 표시합니다. 이전 쿼리의 결과는 http_request.status 필드에 정수 값이 저장되어 있음을 보여줍니다.

JSON 유형이 있는 필드로 필터링

데이터 유형이 JSON인 열에서 값을 추출하려면 JSON_VALUE 함수를 사용합니다.

다음 쿼리를 예로 들어 보겠습니다.

SELECT
  json_payload
FROM
  `TABLE`
WHERE
  json_payload.status IS NOT NULL

SELECT
  json_payload
FROM
  `TABLE`
WHERE
  JSON_VALUE(json_payload.status) IS NOT NULL

이전 쿼리는 json_payload 열의 값을 테스트합니다. 이 열의 콘텐츠는 로그 항목의 콘텐츠에 따라 결정됩니다. 두 쿼리 모두 json_payload 라벨이 지정된 열이 포함되지 않은 행을 삭제합니다. 이 두 쿼리의 차이점은 NULL에 대해 테스트되는 대상을 정의하는 마지막 줄입니다. 이제 행이 두 개 있는 테이블을 예로 들어 보겠습니다. 한 행에서 json_payload 열은 다음 형식을 갖습니다.

{
    status: {
        measureTime: "1661517845"
    }
}

다른 행에서 json_payload 열은 다른 구조입니다.

{
    @type: "type.googleapis.com/google.cloud.scheduler.logging.AttemptFinished"
    jobName: "projects/my-project/locations/us-central1/jobs/test1"
    relativeUrl: "/food=cake"
    status: "NOT_FOUND"
    targetType: "APP_ENGINE_HTTP"
}

이전 행은 모두 json_payload.status IS NOT NULL 제한을 충족합니다. 즉, 쿼리 결과에 두 행이 모두 포함됩니다. 그러나 제한이 JSON_VALUE(json_payload.status) IS NOT NULL인 경우 두 번째 행만 결과에 포함됩니다.

정규 표현식으로 필터링

정규 표현식과 일치하는 하위 문자열을 반환하려면 REGEXP_EXTRACT 함수를 사용합니다. 이 함수의 반환 유형은 STRING 또는 BYTES입니다.

다음 쿼리는 최근에 수신된 로그 항목을 표시하고 json_payload.jobName 필드로 해당 항목을 보관한 후 test로 시작하는 이름의 부분을 표시합니다.

SELECT
  timestamp, REGEXP_EXTRACT(JSON_VALUE(json_payload.jobName), r".*(test.*)$") AS name,
FROM
  `TABLE`
WHERE
  json_payload.jobName IS NOT NULL
ORDER BY timestamp DESC
LIMIT 20

추가 예시는 REGEXP_EXTRACT 문서를 참조하세요. 사용할 수 있는 다른 정규 표현식의 예시는 함수, 연산자, 조건문을 참고하세요.

이 예에 표시된 쿼리는 효율적이지 않습니다. 그림과 같은 하위 문자열 일치의 경우 CONTAINS_SUBSTR 함수를 사용합니다.

로그 항목 그룹화 및 집계

이 섹션에서는 이전 샘플을 바탕으로 테이블 행을 그룹화 및 집계하는 방법을 보여줍니다. 그룹화를 지정하지 않고 집계를 지정하면 SQL이 WHERE 절을 충족하는 모든 행을 단일 그룹으로 취급하므로 단일 결과가 출력됩니다.

모든 SELECT 표현식은 그룹 필드에 포함되거나 집계되어야 합니다.

시간별 그룹화

시간별로 데이터를 그룹화하려면 타임스탬프를 MINUTE과 같이 지정된 단위로 자르는 TIMESTAMP_TRUNC 함수를 사용합니다. 예를 들어 hours:minutes:seconds의 형식으로 지정된 15:30:11의 타임스탬프는 세부사항이 MINUTE로 설정된 경우 15:30:00이 됩니다.

다음 쿼리는 기간 선택도구에서 지정한 간격으로 수신된 데이터를 읽은 다음 json_payload.status 필드 값이 NULL이 아닌 행을 보관합니다. 이 쿼리는 시간별로 각 행의 타임스탬프를 자른 후 잘린 타임스탬프 및 상태로 행을 그룹화합니다.

SELECT
  TIMESTAMP_TRUNC(timestamp, HOUR) AS hour,
  JSON_VALUE(json_payload.status) AS status,
  COUNT(*) AS count
FROM
  `TABLE`
WHERE
  json_payload IS NOT NULL AND
  JSON_VALUE(json_payload.status) IS NOT NULL
GROUP BY hour,status
ORDER BY hour ASC

추가 샘플은 TIMESTAMP_TRUNC 문서를 참조하세요. 다른 시간 기반 함수에 대한 자세한 내용은 날짜/시간 함수를 참조하세요.

리소스별 그룹

다음 쿼리는 최근 1시간 동안의 데이터를 읽은 후 행을 리소스 유형별로 그룹화합니다. 그런 다음 각 유형의 행 수를 계산하고 열이 2개인 테이블을 반환합니다. 첫 번째 열에는 리소스 유형이 나열되고 두 번째 열은 해당 리소스 유형의 행 수입니다.

SELECT
   resource.type, COUNT(*) AS count
FROM
  `TABLE`
GROUP BY resource.type
LIMIT 100

심각도별로 그룹화

다음 쿼리는 최근 1시간 동안의 데이터를 읽은 후 심각도 필드가 있는 행을 보관합니다. 그런 다음 쿼리는 심각도별로 행을 그룹화하고 각 그룹의 행 수를 계산합니다.

SELECT
  severity, COUNT(*) AS count
FROM
  `TABLE`
WHERE
  severity IS NOT NULL
GROUP BY severity
ORDER BY severity
LIMIT 100

그룹화 기준: log_id

다음 쿼리의 결과는 두 개의 열이 있는 테이블입니다. 첫 번째 열에는 로그 이름이 나열되고 두 번째 열에는 로그에 작성된 로그 항목 수가 나열됩니다. 쿼리는 항목 수를 기준으로 결과를 정렬합니다.

SELECT
  log_id, COUNT(*) AS count
FROM
  `TABLE`
GROUP BY log_id
ORDER BY count DESC
LIMIT 100

HTTP 요청의 평균 지연 시간 계산

다음 쿼리는 여러 열을 기준으로 그룹화하고 평균값 계산하는 예시입니다. 이 쿼리는 HTTP 요청에 포함된 URL과 labels.checker_location 필드의 값에 따라 행을 그룹화합니다. 행을 그룹화한 후 쿼리는 각 그룹에 대한 평균 지연 시간을 계산합니다.

SELECT
  JSON_VALUE(labels.checker_location) AS location,
  AVG(http_request.latency.seconds) AS secs, http_request.request_url
FROM
  `TABLE`
WHERE
  http_request IS NOT NULL AND
  http_request.request_method IN ('GET')
GROUP BY http_request.request_url, location
ORDER BY location
LIMIT 100

labels의 데이터 유형이 JSON이므로 이전 표현식에서 JSON_VALUElabels.checker_location 필드 값을 추출해야 합니다. 그러나 이 함수를 사용하여 http_request.latency.seconds 필드에서 값을 추출하지 마세요. 후자의 경우 필드의 데이터 유형은 정수입니다.

서브네트워크 테스트를 위해 전송된 평균 바이트 계산

다음 쿼리는 위치별로 전송되는 평균 바이트 수를 표시하는 방법을 보여줍니다.

쿼리는 최근 1시간 동안의 데이터를 읽은 다음 리소스 유형 열이 gce_subnetwork이고 json_payload 열이 아닌 행만 보관합니다. 그런 다음 쿼리는 리소스의 위치별로 행을 그룹화합니다. 데이터가 숫자로 저장되는 이전 예와 달리 bytes_sent 필드의 값은 문자열이므로 평균을 계산하기 전에 값을 FLOAT64로 변환해야 합니다.

SELECT JSON_VALUE(resource.labels.location) AS location,
   AVG(CAST(JSON_VALUE(json_payload.bytes_sent) AS FLOAT64)) AS bytes
FROM
  `TABLE`
WHERE
  resource.type = "gce_subnetwork" AND
  json_payload IS NOT NULL
GROUP BY location
LIMIT 100

이전 쿼리의 결과는 각 행이 한 위치와 해당 위치에 대해 전송된 평균 바이트를 나열하는 테이블입니다.

JSON 데이터를 검색하고 변환할 수 있는 모든 함수에 대한 자세한 내용은 JSON 함수를 참조하세요.

CAST 및 기타 변환 함수에 관한 자세한 내용은 변환 함수를 참조하세요.

패턴과 일치하는 필드로 로그 항목 수 계산

정규 표현식과 일치하는 하위 문자열을 반환하려면 REGEXP_EXTRACT 함수를 사용합니다. 이 함수의 반환 유형은 STRING 또는 BYTES입니다.

다음 쿼리는 json_payload.jobName 필드의 값이 NULL이 아닌 로그 항목을 보관합니다. 그런 다음 test로 시작하는 이름 서픽스로 항목을 그룹화합니다. 마지막으로 쿼리는 각 그룹의 항목 수를 계산합니다.

SELECT
  REGEXP_EXTRACT(JSON_VALUE(json_payload.jobName), r".*(test.*)$") AS name,
  COUNT(*) AS count
FROM
  `TABLE`
WHERE
  json_payload.jobName IS NOT NULL
GROUP BY name
ORDER BY count
LIMIT 20

추가 예시는 REGEXP_EXTRACT 문서를 참조하세요. 사용할 수 있는 다른 정규 표현식의 예시는 함수, 연산자, 조건문을 참고하세요.

이 섹션에서는 테이블의 여러 열을 검색하는 데 사용할 수 있는 두 가지 접근 방식을 설명합니다.

표에서 검색어 집합과 일치하는 항목을 검색하려면 SEARCH 함수를 사용합니다. 이 함수에는 검색할 위치와 검색어의 두 가지 매개변수가 필요합니다. SEARCH 함수에는 데이터 검색 방법에 대한 특정 규칙이 있으므로 SEARCH 문서를 읽어보는 것이 좋습니다.

다음 쿼리는 '35.193.12.15'와 정확히 일치하는 필드가 있는 행만 보관합니다.

SELECT
  timestamp, log_id, proto_payload, severity, resource.type, resource, labels
FROM
  `TABLE` AS t
WHERE
  proto_payload IS NOT NULL AND
  log_id = "cloudaudit.googleapis.com/data_access" AND
  SEARCH(t,"`35.193.12.15`")
ORDER BY timestamp ASC
LIMIT 20

이전 쿼리에서 백틱은 검색할 값을 래핑합니다. 이렇게 하면 SEARCH 함수가 필드 값과 백틱 사이의 정확한 일치를 검색합니다.

쿼리 문자열에 백틱이 생략되면 SEARCH 문서에 정의된 규칙에 따라 쿼리 문자열이 분할됩니다. 예를 들어 다음 문을 실행하면 쿼리 문자열이 '35', '193', '12', '15'의 4개의 토큰으로 분할됩니다.

  SEARCH(t,"35.193.12.15")

이전 SEARCH 문은 단일 필드가 네 개의 토큰과 모두 일치할 때 행과 일치합니다. 토큰의 순서는 중요하지 않습니다.

쿼리에 여러 SEARCH 문을 포함할 수 있습니다. 예를 들어 이전 쿼리에서 로그 ID의 필터를 다음과 같은 문으로 바꿀 수 있습니다.

  SEARCH(t,"`cloudaudit.googleapis.com/data_access`")

이전 문은 전체 테이블을 검색하는 반면 원래 문은 log_id 열만 검색합니다.

열에 대해 다중 검색을 수행하려면 개별 문자열을 공백으로 구분합니다. 예를 들어 다음 문은 필드에 'Hello World', 'happy', 'days'가 포함된 행과 일치합니다.

  SEARCH(t,"`Hello World` happy days")

마지막으로 전체 테이블을 검색하는 대신 테이블의 특정 열을 검색할 수 있습니다. 예를 들어 다음 문은 text_payloadjson_payload라는 열만 검색합니다.

   SEARCH((text_payload, json_payload) ,"`35.222.132.245`")

SEARCH 함수의 매개변수가 처리되는 방법에 대한 자세한 내용은 BigQuery 참조 페이지 검색 함수를 참조하세요.

표현식에 값이 있는지 여부를 확인하기 위해 대소문자를 구분하지 않는 테스트를 수행하려면 CONTAINS_SUBSTR 함수를 사용합니다. 이 함수는 값이 존재하면 TRUE를 반환하고 그렇지 않으면 FALSE를 반환합니다. 검색 값은 STRING 리터럴이어야 하지만 리터럴 NULL은 아니어야 합니다.

예를 들어 다음 쿼리는 특정 기간에 타임스탬프가 있는 특정 IP 주소가 포함된 모든 데이터 액세스 감사 로그 항목을 가져옵니다. 마지막으로 쿼리는 결과를 정렬한 후 가장 오래된 결과 20개를 표시합니다.

SELECT
  timestamp, log_id, proto_payload, severity, resource.type, resource, labels
FROM
  `TABLE` AS t
WHERE
  proto_payload IS NOT NULL AND
  log_id = "cloudaudit.googleapis.com/data_access" AND
  CONTAINS_SUBSTR(t,"35.193.12.15")
ORDER BY timestamp ASC
LIMIT 20

이전 쿼리는 하위 문자열 테스트를 수행합니다. 따라서 '35.193.12.152'가 포함된 행은 CONTAINS_SUBSTR 문과 일치합니다.

여러 소스의 데이터 결합

쿼리 문은 테이블 또는 표현식을 한 개 이상 검색하고 계산된 결과 행을 반환합니다. 예를 들어 쿼리 문을 사용하여 다양한 방식으로 서로 다른 테이블 또는 데이터 세트에 대한 SELECT 문의 결과를 병합한 후 결합된 데이터에서 해당 열을 선택할 수 있습니다.

조인을 사용하여 두 테이블의 데이터 결합

두 테이블의 정보를 결합하려면 join 연산자 중 하나를 사용합니다. 사용하는 조인의 유형과 조건부 절은 행의 결합 및 삭제 방법을 결정합니다.

다음 쿼리는 동일한 trace 스팬으로 작성된 두 개의 다른 테이블의 행에서 json_payload 필드를 제공합니다. 이 쿼리는 두 테이블의 span_idtrace 열의 값이 일치하는 행에 대해 두 테이블에서 내부 JOIN을 수행합니다. 이 결과에서 쿼리는 TABLE_1에서 가져온 timestamp, severityjson_payload 필드, TABLE_2에서 가져온 json_payload 필드, 두 테이블이 조인된 span_idtrace 필드의 값을 선택하고 최대 100개의 행을 반환합니다.

SELECT
  a.timestamp, a.severity, a.json_payload, b.json_payload, a.span_id, a.trace
FROM `TABLE_1` a
JOIN `TABLE_2` b
ON
  a.span_id = b.span_id AND
  a.trace = b.trace
LIMIT 100

union으로 여러 선택 항목 결합

두 개 이상의 SELECT 문의 결과를 결합하고 중복 행을 삭제하려면 UNION 연산자를 사용합니다. 중복 행을 유지하려면 UNION ALL 연산자를 사용합니다.

다음 쿼리는 TABLE_1에서 최근 1시간 분량의 데이터를 읽고 결과를 TABLE_2의 최근 1시간 데이터와 병합하고 타임스탬프를 늘려 병합된 데이터를 정렬한 다음 가장 오래된 100개 항목을 표시합니다.

SELECT
  timestamp, log_name, severity, json_payload, resource, labels
FROM(
  SELECT * FROM `TABLE_1`
  UNION ALL
  SELECT * FROM `TABLE_2`
)
ORDER BY timestamp ASC
LIMIT 100

다음 단계

로그 항목을 라우팅하고 저장하는 방법에 대한 자세한 내용은 다음 문서를 참조하세요.

SQL 참조 문서는 다음 문서를 참조하세요.