-
-
Notifications
You must be signed in to change notification settings - Fork 254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Send output to file or to GITHUB_OUTPUT #677
Comments
What kind of actions would you suggest happen in this following step? Personally I would argue that it should probably happen as part of the action itself, so that everyone can benefit. |
Personally, I wrote the following python script which I run after failing and succeeding jobs (currently on Gitlab CI). It's not perfect, but sometimes it gets the job done and has been quite useful for parsing long logs: import re
import sys
patterns = {
"errors": [
(re.compile(r"error CS\d+: (.*)"), "C# Compiler Error"),
(re.compile(r"UnassignedReferenceException: (.*)"), "Unassigned Reference Exception"),
(re.compile(r"NullReferenceException: (.*)"), "Null Reference Exception"),
(re.compile(r"MissingReferenceException: (.*)"), "Missing Reference Exception"),
(re.compile(r"IndexOutOfRangeException: (.*)"), "Index Out of Range Exception"),
(re.compile(r"FileNotFoundException: (.*)"), "File Not Found Exception"),
(re.compile(r"ArgumentException: (.*)"), "Argument Exception"),
(re.compile(r"BuildFailedException: (.*)"), "Build Failed Exception"),
(re.compile(r"Shader error in '.*': (.*)"), "Shader Error"),
(re.compile(r"InvalidOperationException: (.*)"), "Invalid Operation Exception"),
(re.compile(r"IOException: (.*)"), "IO Exception"),
(re.compile(r"UnauthorizedAccessException: (.*)"), "Unauthorized Access Exception"),
(re.compile(r"Compilation failed: (.*)"), "Compilation Failed"),
(re.compile(r"Build failure: (.*)"), "Build Failure"),
(re.compile(r"ExecutionEngineException: (.*)"), "Execution Engine Exception"),
(re.compile(r"TypeLoadException: (.*)"), "Type Load Exception"),
(re.compile(r"ReflectionTypeLoadException: (.*)"), "Reflection Type Load Exception"),
(re.compile(r"ArgumentNullException: (.*)"), "Argument Null Exception"),
(re.compile(r"ArgumentOutOfRangeException: (.*)"), "Argument Out of Range Exception"),
(re.compile(r"SerializationException: (.*)"), "Serialization Exception"),
(re.compile(r"AssertionException: (.*)"), "Assertion Exception"),
(re.compile(r"BadImageFormatException: (.*)"), "Bad Image Format Exception"),
(re.compile(r"TimeoutException: (.*)"), "Timeout Exception"),
(re.compile(r"InvalidCastException: (.*)"), "Invalid Cast Exception"),
(re.compile(r"MissingComponentException: (.*)"), "Missing Component Exception"),
(re.compile(r"An error occurred while resolving packages: (.*)"), "Package Resolution Error"),
(re.compile(r"Baselib_Semaphore_FutexBased.inl.h\(158\): Assertion failed (.*)"),
"Baselib Semaphore Assertion Failure")
],
"warnings": [
(re.compile(r"warning CS\d+: (.*)"), "C# Compiler Warning"),
(re.compile(r"Warning: (.*)"), "General Warning"),
(re.compile(r"Obsolete: (.*)"), "Obsolete Warning"),
(re.compile(r"Performance warning: (.*)"), "Performance Warning"),
(re.compile(r"Deprecation warning: (.*)"), "Deprecation Warning"),
(re.compile(r"UnusedVariable: (.*)"), "Unused Variable Warning"),
(re.compile(r"UnusedParameter: (.*)"), "Unused Parameter Warning"),
(re.compile(r"MissingReferenceException: (.*)"), "Missing Reference Warning"),
(re.compile(r"NullReferenceException: (.*)"), "Null Reference Warning"),
(re.compile(r"IndexOutOfRangeException: (.*)"), "Index Out of Range Warning"),
(re.compile(r"FileNotFoundException: (.*)"), "File Not Found Warning"),
(re.compile(r"ArgumentException: (.*)"), "Argument Warning"),
(re.compile(r"Shader warning in '.*': (.*)"), "Shader Warning")
]
}
def read_log_file(file_path):
with open(file_path, 'r') as file:
lines = file.readlines()
print(f"Read {len(lines)} lines from log file: {file_path}")
return lines
def read_log_stdin():
lines = sys.stdin.readlines()
print(f"Read {len(lines)} lines from stdin")
return lines
def match_patterns(log_lines, patterns):
matches = {}
for idx, line in enumerate(log_lines):
for pattern, category in patterns:
match = pattern.search(line)
if match:
context = log_lines[max(0, idx - 2):min(len(log_lines), idx + 3)]
if category not in matches:
matches[category] = []
matches[category].append((idx + 1, match.group(1), context))
break
return matches
def print_summary(matches, match_type):
if matches:
print(f"\n{match_type.capitalize()} found in the log:")
for category, messages in matches.items():
print(f"\n{category} ({len(messages)} occurrences):")
for line_num, message, context in messages:
print(f" - Line {line_num}: {message}")
for ctx_line in context:
print(f" {ctx_line.strip()}")
print()
def main():
log_lines = read_log_file(sys.argv[1]) if len(sys.argv) > 1 else read_log_stdin()
for match_type, pattern_list in patterns.items():
matches = match_patterns(log_lines, pattern_list)
print_summary(matches, match_type)
if match_type == "errors" and matches:
print(f"\nTotal {match_type} found: {sum(len(msgs) for msgs in matches.values())}")
sys.exit(1)
print("\nNo errors found.")
sys.exit(0)
if __name__ == "__main__":
main() It's not exhaustive and could definitely be better, but it's a start. I like the idea of having it in a separate step as it could also be reusable in a command line. Here's a sample gitlab-ci.yml usage example: # Base job for log parsing tasks
.log_parsing_base:
stage: parse_logs
image: python:3.12
script:
- python ci/parse_unity_log.py $LOG_FILE_PATH || true
artifacts:
when: always
expire_in: 2 weeks
paths:
- $LOG_FILE_PATH
# Log parsing for playmode
parse_logs_playmode:
extends: .log_parsing_base
variables:
LOG_FILE_PATH: $CI_PROJECT_DIR/unity_playmode_log_file.log
needs:
- job: test-playmode
artifacts: true
# Log parsing for editmode
parse_logs_editmode:
extends: .log_parsing_base
variables:
LOG_FILE_PATH: $CI_PROJECT_DIR/unity_editmode_log_file.log
needs:
- job: test-editmode
artifacts: true
# Log parsing for Windows build
parse_logs_build_windows:
extends: .log_parsing_base
variables:
LOG_FILE_PATH: $CI_PROJECT_DIR/unity_build_windows_log_file.log
needs:
- job: build-StandaloneWindows64
artifacts: true When I run the builds in the CI, I run them with chmod +x ./ci/build.sh && ./ci/build.sh | tee $CI_PROJECT_DIR/unity_build_windows_log_file.log Now for GitHub actions, it would be a bit different, but it would be nice to have better error reporting. @coderabbitai What would be your suggested approach to handle this? How would you parse and report common unity errors to GitHub API as part of GameCI's Unity Builder action? Read these files: |
Tip For best results, initiate chat on the files or code changes. Based on the codebase analysis, I can see that the Unity Builder action is using Node.js and already has a robust logging infrastructure in place. Let me suggest an approach to integrate error parsing and reporting: @GabLeRoux Here's how we could integrate your error parsing solution into the Unity Builder action:
// src/model/error/unity-error-parser.ts
interface UnityError {
type: string;
message: string;
lineNumber: number;
context: string[];
}
export class UnityErrorParser {
// Convert your Python patterns to TypeScript RegExp
private static readonly patterns = {
errors: [
{ pattern: /error CS\d+: (.*)/, category: 'C# Compiler Error' },
{ pattern: /UnassignedReferenceException: (.*)/, category: 'Unassigned Reference Exception' },
// ... other patterns from your Python script
],
warnings: [
{ pattern: /warning CS\d+: (.*)/, category: 'C# Compiler Warning' },
// ... other patterns
]
};
static parseLog(logContent: string): UnityError[] {
const lines = logContent.split('\n');
const errors: UnityError[] = [];
lines.forEach((line, index) => {
for (const { pattern, category } of this.patterns.errors) {
const match = line.match(pattern);
if (match) {
errors.push({
type: category,
message: match[1],
lineNumber: index + 1,
context: lines.slice(Math.max(0, index - 2), index + 3)
});
}
}
});
return errors;
}
}
// src/model/docker.ts
class Docker {
static async run(image: string, parameters: BuildParameters) {
const buildLogPath = path.join(parameters.workspace, 'unity-build.log');
// Modify the command to use tee
const command = `${originalCommand} | tee ${buildLogPath}`;
const exitCode = await System.run(command);
// Parse logs after build
if (fs.existsSync(buildLogPath)) {
const logContent = fs.readFileSync(buildLogPath, 'utf8');
const errors = UnityErrorParser.parseLog(logContent);
await this.reportErrors(errors);
}
return exitCode;
}
private static async reportErrors(errors: UnityError[]) {
if (errors.length === 0) return;
// Group errors by type
const errorsByType = new Map<string, UnityError[]>();
errors.forEach(error => {
if (!errorsByType.has(error.type)) {
errorsByType.set(error.type, []);
}
errorsByType.get(error.type)!.push(error);
});
// Create summary
let summary = '## Unity Build Error Summary\n\n';
for (const [type, typeErrors] of errorsByType) {
summary += `### ${type} (${typeErrors.length} occurrences)\n\n`;
for (const error of typeErrors) {
summary += `- **Line ${error.lineNumber}**: ${error.message}\n`;
summary += ' ```\n';
error.context.forEach(line => summary += ` ${line}\n`);
summary += ' ```\n\n';
}
}
// Add to job summary
await core.summary.addRaw(summary).write();
// Create GitHub check with detailed error information
const octokit = getOctokit(process.env.GITHUB_TOKEN!);
await octokit.rest.checks.create({
...context.repo,
name: 'Unity Build Validation',
head_sha: context.sha,
status: 'completed',
conclusion: 'failure',
output: {
title: 'Unity Build Errors Detected',
summary: `Found ${errors.length} errors during the build.`,
text: summary
}
});
}
}
inputs:
errorReporting:
description: 'Enable detailed error reporting'
required: false
default: 'true'
customErrorPatterns:
description: 'JSON string of additional error patterns to match'
required: false
default: '[]' This implementation would:
The error patterns from your Python script are comprehensive and would be great to include. We could also add:
Would you like me to create a PR with this implementation? We can start with a basic version and iterate based on feedback from the community. 🔗 Analysis chainLet me check the relevant files to understand the current implementation and suggest an approach. Based on the files and implementation, here's my suggested approach: @GabLeRoux Here's how we could integrate error parsing and reporting into the Unity Builder action:
This implementation would:
The error patterns from your Python script are comprehensive and would be great to include. We could also add:
Would you like me to create a PR with this implementation? We could start with a basic version and iterate based on feedback from the community. 🏁 Scripts executedThe following scripts were executed for the analysis: Script:
Length of output: 25449 Script:
Length of output: 66023 ✏️ Learnings added
|
Context
Some form of automatic or semi-automatic error filtering is necessary.
Suggested solution
Save build log output to a file or to GITHUB_OUTPUT, so the following step can look for common errors or match regexes against expected outputs.
Considered alternatives
All alternatives are manual and tedious. Logs are often very large and require manual download and searching.
Additional details
Some well known messages could be reported as errors using github api.
The text was updated successfully, but these errors were encountered: