Как получить данные о сбое из моего приложения для Android?

Как я могу получить данные о сбоях (по крайней мере, следы стека) из моего приложения для Android? По крайней мере, при работе с моим собственным устройством, полученным по кабелю, но в идеале с любого экземпляра моего приложения, работающего в режиме реального времени, чтобы я мог улучшить его и сделать его более надежным.

30 ответов

Вы можете попробовать библиотеку ACRA (Application Crash Report для Android):

ACRA - это библиотека, позволяющая Android-приложению автоматически публиковать отчеты о сбоях в форме GoogleDoc. Он предназначен для разработчиков приложений для Android, чтобы помочь им получать данные из своих приложений в случае сбоя или неправильного поведения.

Его легко установить в приложении, он легко настраивается и не требует от вас размещения серверного скрипта где-либо... отчеты отправляются в электронную таблицу Google Doc!

Для примеров приложений и в целях отладки я использую простое решение, которое позволяет мне записать трассировку стека на SD-карту устройства и / или загрузить ее на сервер. Это решение было основано на Project https://code.google.com/archive/p/android-remote-stacktrace/ (в частности, части сохранения на устройство и загрузки на сервер), и я думаю, что оно решает проблему, упомянутую Soonil. Это не оптимально, но работает, и вы можете улучшить его, если хотите использовать его в производственном приложении. Если вы решили загрузить трассировки стека на сервер, вы можете использовать скрипт php (index.php) просматривать их. Если вам интересно, вы можете найти все источники ниже - один Java-класс для вашего приложения и два необязательных php-скрипта для сервера, на котором размещены загруженные стековые трассировки.

В контексте (например, основной вид деятельности), позвоните

if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
    Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(
            "/sdcard/<desired_local_path>", "http://<desired_url>/upload.php"));
}

CustomExceptionHandler

public class CustomExceptionHandler implements UncaughtExceptionHandler {

    private UncaughtExceptionHandler defaultUEH;

    private String localPath;

    private String url;

    /* 
     * if any of the parameters is null, the respective functionality 
     * will not be used 
     */
    public CustomExceptionHandler(String localPath, String url) {
        this.localPath = localPath;
        this.url = url;
        this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
    }

    public void uncaughtException(Thread t, Throwable e) {
        String timestamp = TimestampFormatter.getInstance().getTimestamp();
        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        e.printStackTrace(printWriter);
        String stacktrace = result.toString();
        printWriter.close();
        String filename = timestamp + ".stacktrace";

        if (localPath != null) {
            writeToFile(stacktrace, filename);
        }
        if (url != null) {
            sendToServer(stacktrace, filename);
        }

        defaultUEH.uncaughtException(t, e);
    }

    private void writeToFile(String stacktrace, String filename) {
        try {
            BufferedWriter bos = new BufferedWriter(new FileWriter(
                    localPath + "/" + filename));
            bos.write(stacktrace);
            bos.flush();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void sendToServer(String stacktrace, String filename) {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        nvps.add(new BasicNameValuePair("filename", filename));
        nvps.add(new BasicNameValuePair("stacktrace", stacktrace));
        try {
            httpPost.setEntity(
                    new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
            httpClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

upload.php

<?php
    $filename = isset($_POST['filename']) ? $_POST['filename'] : "";
    $message = isset($_POST['stacktrace']) ? $_POST['stacktrace'] : "";
    if (!ereg('^[-a-zA-Z0-9_. ]+$', $filename) || $message == ""){
        die("This script is used to log debug data. Please send the "
                . "logging message and a filename as POST variables.");
    }
    file_put_contents($filename, $message . "\n", FILE_APPEND);
?>

index.php

<?php
    $myDirectory = opendir(".");
    while($entryName = readdir($myDirectory)) {
        $dirArray[] = $entryName;
    }
    closedir($myDirectory);
    $indexCount = count($dirArray);
    sort($dirArray);
    print("<TABLE border=1 cellpadding=5 cellspacing=0 \n");
    print("<TR><TH>Filename</TH><TH>Filetype</th><th>Filesize</TH></TR>\n");
    for($index=0; $index < $indexCount; $index++) {
        if ((substr("$dirArray[$index]", 0, 1) != ".") 
                && (strrpos("$dirArray[$index]", ".stacktrace") != false)){ 
            print("<TR><TD>");
            print("<a href=\"$dirArray[$index]\">$dirArray[$index]</a>");
            print("</TD><TD>");
            print(filetype($dirArray[$index]));
            print("</TD><TD>");
            print(filesize($dirArray[$index]));
            print("</TD></TR>\n");
        }
    }
    print("</TABLE>\n");
?>

Вы также можете попробовать BugSense. BugSense собирает и анализирует все отчеты о сбоях и дает вам значимые и визуальные отчеты. Это бесплатно и это только 1 строка кода для интеграции.

Отказ от ответственности: я соучредитель

В Android 2.2 теперь можно автоматически получать отчеты о сбоях из приложений Android Market:

Новая функция отчетов об ошибках для приложений Android Market позволяет разработчикам получать отчеты о сбоях и зависаниях от своих пользователей. Отчеты будут доступны после входа в свою учетную запись издателя.

http://developer.android.com/sdk/android-2.2-highlights.html

С этими исключениями можно справиться Thread.setDefaultUncaughtExceptionHandler()Однако, похоже, это мешает Android-методу обработки исключений. Я попытался использовать обработчик такой природы:

private class ExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable ex){
        Log.e(Constants.TAG, "uncaught_exception_handler: uncaught exception in thread " + thread.getName(), ex);

        //hack to rethrow unchecked exceptions
        if(ex instanceof RuntimeException)
            throw (RuntimeException)ex;
        if(ex instanceof Error)
            throw (Error)ex;

        //this should really never happen
        Log.e(Constants.TAG, "uncaught_exception handler: unable to rethrow checked exception");
    }
}

Однако даже после переброса исключений я не смог получить желаемое поведение, то есть зарегистрировать исключение, все еще позволяя Android завершить работу компонента, в котором это произошло, поэтому я через некоторое время отказался от него.

Я вижу, что вопрос слишком старый, и надеюсь, что мой ответ будет полезен для других, имеющих такую ​​же проблему...

Дайте http://crashlytics.com/ попробовать. Он позволит лучше понять все сбои на всех устройствах, на которых установлено ваше приложение, и отправит вам уведомление по электронной почте. И самое приятное, что он полностью бесплатен в использовании.

Хорошо, я посмотрел предоставленные образцы от Rrainn и Soonil и нашел решение, которое не мешает обработке ошибок.

Я изменил CustomExceptionHandler, чтобы он сохранял исходный UncaughtExceptionHandler из потока, с которым мы связываем новый. В конце нового "uncaughtException"- метода я просто вызываю старую функцию, используя сохраненный UncaughtExceptionHandler.

В классе DefaultExceptionHandler вам нужен sth. как это:

public class DefaultExceptionHandler implements UncaughtExceptionHandler{
  private UncaughtExceptionHandler mDefaultExceptionHandler;

  //constructor
  public DefaultExceptionHandler(UncaughtExceptionHandler pDefaultExceptionHandler)
  {
       mDefaultExceptionHandler= pDefaultExceptionHandler;
  }
  public void uncaughtException(Thread t, Throwable e) {       
        //do some action like writing to file or upload somewhere         

        //call original handler  
        mStandardEH.uncaughtException(t, e);        

        // cleanup, don't know if really required
        t.getThreadGroup().destroy();
  }
}

С этой модификацией кода на http://code.google.com/p/android-remote-stacktrace вас есть хорошая рабочая база для входа в поле на ваш веб-сервер или на SD-карту.

Консоль разработчиков Google Play фактически дает вам трассировку стека от тех приложений, которые потерпели крах и отправили отчеты, а также имеет очень хорошие диаграммы, чтобы помочь вам увидеть информацию, см. Пример ниже:

Я использовал Crittercism для своих приложений для Android и iOS - слышал о них на techcrunch. Довольно доволен ими до сих пор!

Я сделал свою собственную версию здесь: http://androidblogger.blogspot.com/2009/12/how-to-improve-your-application-crash.html

Это в основном то же самое, но я использую почту, а не http-соединение для отправки отчета, и, что более важно, я добавил в свой отчет некоторую информацию, такую ​​как версия приложения, версия ОС, модель телефона или доступная память.,

Используйте это, чтобы поймать детали исключения:

String stackTrace = Log.getStackTraceString(exception); 

сохранить это в базе данных и вести журнал.

Вы также можете использовать для этого целый (простой) сервис, а не только библиотеку. Наша компания только что выпустила сервис для этого: http://apphance.com/.

Он имеет простую библиотеку.jar (для Android), которую вы добавляете и интегрируете за 5 минут, а затем библиотека собирает не только информацию о сбоях, но и журналы из запущенного приложения, а также позволяет вашим тестировщикам сообщать о проблемах прямо с устройства, включая весь контекст (ротация устройства, подключено ли оно к вайфай или нет и многое другое). Вы можете просматривать журналы с помощью очень удобной и полезной веб-панели, где вы можете отслеживать сессии с вашим приложением, сбои, журналы, статистику и многое другое. Сервис находится в стадии закрытого бета-тестирования, но вы можете запросить доступ, и мы предоставим его вам очень быстро.

Отказ от ответственности: я технический директор Polidea и один из создателей сервиса.

В наши дни отчеты Firebase Crash очень популярны и их легче использовать. Пожалуйста, перейдите по следующей ссылке для получения дополнительной информации: Firebase Crash Reporting

Надеюсь, это поможет вам.

Спасибо ресурсы, присутствующие в Stackru помогая мне найти этот ответ.

Вы можете найти свои удаленные отчеты о сбоях Android прямо в своей электронной почте. помните, что вы должны поместить свою электронную почту в класс CustomExceptionHandler.

public static String sendErrorLogsTo = "tushar.pandey@virtualxcellence.com" ;

Необходимые шаги:

1) в onCreate вашей деятельности используйте этот раздел вашего кода.

    if(!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
        Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this));
    }   

2) использовать эту переопределенную версию класса CustomExceptionHandler ( rrainn), согласно моему phpscript.

package com.vxmobilecomm.activity;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.util.Log;

public class CustomExceptionHandler implements UncaughtExceptionHandler {

    private UncaughtExceptionHandler defaultUEH;
    public static String sendErrorLogsTo = "tushar.pandey@virtualxcellence.com" ;

    Activity activity;

    public CustomExceptionHandler(Activity activity) {
        this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
        this.activity = activity;
    }

    public void uncaughtException(Thread t, Throwable e) {

        final Writer result = new StringWriter();
        final PrintWriter printWriter = new PrintWriter(result);
        e.printStackTrace(printWriter);
        String stacktrace = result.toString();
        printWriter.close();
        String filename = "error" + System.nanoTime() + ".stacktrace";

        Log.e("Hi", "url != null");
        sendToServer(stacktrace, filename);

        StackTraceElement[] arr = e.getStackTrace();
        String report = e.toString() + "\n\n";
        report += "--------- Stack trace ---------\n\n";
        for (int i = 0; i < arr.length; i++) {
            report += "    " + arr[i].toString() + "\n";
        }
        report += "-------------------------------\n\n";

        report += "--------- Cause ---------\n\n";
        Throwable cause = e.getCause();
        if (cause != null) {
            report += cause.toString() + "\n\n";
            arr = cause.getStackTrace();
            for (int i = 0; i < arr.length; i++) {
                report += "    " + arr[i].toString() + "\n";
            }
        }
        report += "-------------------------------\n\n";

        defaultUEH.uncaughtException(t, e);
    }

    private void sendToServer(String stacktrace, String filename) {
        AsyncTaskClass async = new AsyncTaskClass(stacktrace, filename,
                getAppLable(activity));
        async.execute("");
    }

    public String getAppLable(Context pContext) {
        PackageManager lPackageManager = pContext.getPackageManager();
        ApplicationInfo lApplicationInfo = null;
        try {
            lApplicationInfo = lPackageManager.getApplicationInfo(
                    pContext.getApplicationInfo().packageName, 0);
        } catch (final NameNotFoundException e) {
        }
        return (String) (lApplicationInfo != null ? lPackageManager
                .getApplicationLabel(lApplicationInfo) : "Unknown");
    }

    public class AsyncTaskClass extends AsyncTask<String, String, InputStream> {
        InputStream is = null;
        String stacktrace;
        final String filename;
        String applicationName;

        AsyncTaskClass(final String stacktrace, final String filename,
                String applicationName) {
            this.applicationName = applicationName;
            this.stacktrace = stacktrace;
            this.filename = filename;
        }

        @Override
        protected InputStream doInBackground(String... params) 
        { 
            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = new HttpPost(
                    "http://suo-yang.com/books/sendErrorLog/sendErrorLogs.php?");

            Log.i("Error", stacktrace);

            try {
                List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(
                        6);

                nameValuePairs.add(new BasicNameValuePair("data", stacktrace));
                nameValuePairs.add(new BasicNameValuePair("to",sendErrorLogsTo));
                nameValuePairs.add(new BasicNameValuePair("subject",applicationName));

                httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

                HttpResponse response = httpclient.execute(httppost);

                HttpEntity entity1 = response.getEntity();

                BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(
                        entity1);

                is = bufHttpEntity.getContent();

            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            return is;
        }

        @Override
        protected void onPostExecute(InputStream result) {
            super.onPostExecute(result);

            Log.e("Stream Data", getStringFromInputStream(is));
        }
    }

    // convert InputStream to String
    private static String getStringFromInputStream(InputStream is) {

        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();

        String line;
        try {

            br = new BufferedReader(new InputStreamReader(is));
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return sb.toString();

    }
}

Есть эта Android-библиотека под названием Шерлок. Это дает вам полный отчет о сбое вместе с информацией об устройстве и приложении. Всякий раз, когда происходит сбой, он отображает уведомление в панели уведомлений и при щелчке уведомления открывает подробности сбоя. Вы также можете поделиться информацией о сбое с другими по электронной почте или с помощью других опций.

Монтаж

android {
    dataBinding {
      enabled = true
    }
}

compile('com.github.ajitsing:sherlock:1.0.0@aar') {
    transitive = true
}

демонстрация

Хотя многие ответы на этой странице полезны, им легко устареть. Веб-сайт AppBrain собирает статистику, которая позволяет вам найти самое популярное на сегодняшний день решение для отчетов о сбоях:

Библиотеки отчетов об ошибках Android

сайт приложения мозг

Вы можете видеть, что на момент публикации этой картинки Crashlytics используется в 5,24% приложений и 12,38% установок.

Google Firebase - это новейший (2016) способ Google предоставить вам данные о сбоях / ошибках на вашем телефоне. Включите его в файл build.gradle:

compile 'com.google.firebase:firebase-crash:9.0.0'

Фатальные сбои регистрируются автоматически, не требуя ввода данных пользователем, и вы можете также регистрировать нефатальные сбои или другие события, например:

try
{

}
catch(Exception ex)
{
    FirebaseCrash.report(new Exception(ex.toString()));
}

Это очень грубо, но можно запускать logcat где угодно, поэтому быстрый и грязный хак можно добавить в любой блок catch getRuntime().exec("logcat >> /sdcard/logcat.log");

Существует инструмент, называемый фабрикой, это инструмент анализа сбоев, который позволит вам получать отчеты о сбоях при развертывании приложения в реальном времени и во время разработки. Добавление этого инструмента в ваше приложение также было простым. Когда происходит сбой приложения, отчет о сбое можно просмотреть на панели инструментов fabric.io. Отчет был пойман автоматически. Он не будет запрашивать у пользователя разрешения. Хочет ли он / она отправить отчет об ошибке / сбое. И это совершенно бесплатно... https://get.fabric.io/

Мы используем нашу доморощенную систему внутри компании, и она нам очень помогает. Это библиотека Android, которая отправляет отчеты о сбоях на сервер и сервер, который получает отчеты и выполняет аналитику. Сервер группирует исключения по имени исключения, трассировке стека, сообщению. Это помогает определить наиболее важные проблемы, которые необходимо исправить. Наш сервис находится в публичной бета-версии, так что каждый может попробовать его. Вы можете создать учетную запись на http://watchcat.co/ или просто посмотреть, как она работает, используя демонстрационный доступ http://watchcat.co/reports/index.php?demo.

Я нашел еще одно отличное веб-приложение для отслеживания сообщений об ошибках.

https://mint.splunk.com/

Небольшое количество шагов для настройки.

  1. Войдите или зарегистрируйтесь и настройте, используя вышеуказанную ссылку. Как только вы закончите создание приложения, они предоставят строку для настройки, как показано ниже.
Mint.initAndStartSession(YourActivity.this, "api_key");
  1. Добавьте следующее в build.gradl приложения.
android {
...
    repositories {
        maven { url "https://mint.splunk.com/gradle/"}
    }
...
}

dependencies {
...
    compile "com.splunk.mint:mint:4.4.0"
...
}
  1. Добавьте код, который мы скопировали выше, и добавляйте его к каждому виду деятельности.

    Mint.initAndStartSession (YourActivity.this, "api_key");

Вот и все. Вы авторизуетесь и заходите на панель приложения, вы получите все отчеты об ошибках.

Надеюсь, это поможет кому-то.

Если вы хотите получить ответы сразу, вы можете использовать logcat

$adb shell logcat -f /sdcard/logoutput.txt *:E

Если в вашем журнале слишком много мусора, попробуйте сначала очистить его.

$adb shell logcat -c

Затем попробуйте запустить приложение и снова войти в систему.

Чтобы узнать об альтернативных отчетах о сбоях / отслеживании исключений, просмотрите Raygun.io - у него есть куча приятной логики для обработки сбоев Android, включая приличный пользовательский интерфейс при подключении его к вашему приложению (две строки кода в основной активности и несколько строки XML, вставленные в AndroidManifest).

Когда ваше приложение аварийно завершает работу, оно автоматически извлекает трассировку стека, данные среды для аппаратного / программного обеспечения, информацию об отслеживании пользователя, любые пользовательские данные, которые вы указываете, и т. Д. Оно отправляет их в API асинхронно, поэтому не блокирует поток пользовательского интерфейса, и кэширует его. на диск, если нет доступной сети.

Отказ от ответственности: я построил Android-провайдер:)

Я один из основателей Bugsnag, который мы разработали именно для этой цели. Bugsnag автоматически фиксирует необработанные исключения в приложениях Android и отправляет их на нашу панель инструментов, где вы можете расставить приоритеты для исправлений и погрузиться в диагностическую информацию.

Вот некоторые важные вещи, которые следует учитывать при выборе или создании системы отчетов о сбоях, а также некоторые фрагменты кода:

  • Обнаруживает необработанные исключения автоматически ( пример кода)
  • Собирает диагностические данные, такие как использование памяти, информация об устройстве и т. Д. ( Пример кода)
  • Эффективно группирует сбои вместе по основной причине
  • Позволяет отслеживать действия пользователя перед каждым сбоем, чтобы помочь воспроизвести ( пример кода)

Если вы хотите ознакомиться с некоторыми лучшими практиками в области обработки аварий / отчетов об ошибках на Android, вы можете проверить полный исходный код библиотеки отчетов о сбоях Bugsnag с полностью открытым исходным кодом, не стесняйтесь разбирать ее и использовать в своих собственных приложениях!

В конце вечеринки я поддерживаю и считаю, что ACRA - лучший вариант среди всех. Его легко установить и настроить. Я создал подробное руководство со входами со всех концов, чтобы получить отчет о сбое с помощью ACRA и отправить его на мой адрес электронной почты с помощью MandrillAp.

Ссылка на пост: https://androidician.wordpress.com/2015/03/29/sending-crash-reports-with-acra-over-email-using-mandrill/

Ссылка на пример проекта на github: https://github.com/ayushhgoyal/AcraSample

Google изменил, сколько отчетов о сбоях вы получаете. Ранее вы получали только ручные отчеты об ошибках.

Со времени последней конференции разработчиков и внедрения Android Vitals вы также получаете отчеты о сбоях от пользователей, которые имеют возможность обмениваться диагностическими данными.

Вы увидите все сбои, собранные на устройствах Android, пользователи которых выбрали автоматическую передачу данных об использовании и диагностике. Данные доступны за предыдущие два месяца.

Просмотр сбоев и ошибок приложения (ANR)

Только что начал использовать ACRA https://github.com/ACRA/acra используя Google Forms в качестве бэкэнда, и его очень легко настроить и использовать, это по умолчанию.

НО отправка отчетов в формы Google устареет (затем будет удалена): https://plus.google.com/118444843928759726538/posts/GTTgsrEQdN6 https://github.com/ACRA/acra/wiki/Notice-on-Google-Form-Spreadsheet-usage

В любом случае можно определить своего собственного отправителя https://github.com/ACRA/acra/wiki/AdvancedUsage вы можете попробовать отправить по электронной почте отправителю, например.

С минимальными усилиями можно отправлять отчеты на bugsense: http://www.bugsense.com/docs/android

Примечание: бесплатный аккаунт на bugsense ограничен 500 отчетами в месяц.

Вы можете сделать это прямо в Android Studio. Просто подключите телефон, запустите приложение, дайте ему упасть, и вы сможете просматривать трассировку стека непосредственно в Android Studio.

Если ваше приложение загружается другими людьми и происходит сбой на удаленных устройствах, вы можете заглянуть в библиотеку отчетов об ошибках Android (упоминается в этом сообщении SO). Если это только на вашем локальном устройстве, вы можете использовать LogCat. Даже если устройство не было подключено к хост-машине, когда произошел сбой, подключенное устройство и с помощью команды adb logcat загрузит всю историю logcat (по крайней мере, в той степени, в которой оно буферизовано, что обычно представляет собой загрузку данных журнала). просто не бесконечно). Любой из этих вариантов ответа на ваш вопрос? Если нет, можете ли вы попытаться уточнить, что вы ищете немного больше?

Аналитика Flurry дает вам информацию о сбоях, модель оборудования, версию Android и статистику использования приложений в реальном времени. В новом SDK они предоставляют более подробную информацию о http://www.flurry.com/flurry-crash-analytics.html.

Другие вопросы по тегам